文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何利用快排的小技巧,解决算法难题?

2024-11-30 07:43

关注

今天就为大家带来面试中经常出现排序算法的深度解析。

快速排序本质上是一个前序遍历

上一篇文章中讲到,合并排序本质上和二叉树后续遍历非常类似,而快速排序本质上和二叉树的前序遍历非常类似。

首先你还先回忆一下二叉树的前序遍历:

// 递归
function preOrder(root, array = []) {
  if (root === null) return null;
  array.push(root.val);
  postOrder(root.left, array);
  postOrder(root.right, array);
}

这里可以将代码拆分为三部分:

// 边界处理
if (root === null) return null;
// 根节点信息处理
array.push(root.val);
// 根节点的信息,传递给左右子树。
postOrder(root.left, array);
postOrder(root.right, array);

边界处理先不提,二叉树的前序遍历,有两个重点的特点:

简单利用伪代码表示就是:

function 前序遍历():
    获取根节点信息;
    将根节点的信息传递左右子树/左右子数组;

快速排序和前序遍历类似,这个伪代码对于快速排序同样成立。

并且对于排序算法来说,排序也就意味着有序,有序性就是信息,因此,我们要做的事情就是把能拿到的有序信息,传递给左子数组和右子数组。

有序性

那在排序算法中,如果利用有序性了? 其实有序性的就是选择一个数 X,并且利用这个数,将数组分成三部分:

左右子树/子数组的处理

对于到二叉树来说,小于 X 的部分也就是二叉树的左子树,等于 X 的部分就是二叉树的根节点,大于 X 的部分就是二叉树的右子树。

二叉树对于子树的处理,就是利用递归的方式来进行处理。

postOrder(root.left);
postOrder(root.right);

排序算法对于子数组的处理,同样也是递归地处理左子数组和右子数组。 相对于二叉树的前序遍历来说,快速排序的左右子区间是由切分动态生成的,并不像二叉树那样由指针固定。并且对于根结点的处理,需要执行“三路切分”操作,将一个数组切分为三段;

所以总结一下,又讲回到前面的伪代码:

function 前序遍历/快速排序():
    获取根节点信息;
    将根节点的信息传递左右子树/左右子数组;

并且前序遍历/快速排序的特点可以总结为以下 3 点:

1. 划分子结构

对于二叉树而言,子树的划分是天然的,已经在数据结构里面约定好了,比如 Node.left、Node.right。

root.left
root.right 

可以直接通过树的子节点拿

但是对于数组而言,划分子结构,也就是找一个节点,将数组切分为几份,切分的时候,如果想到达最优的效率,那么将数组切为平均的两半效率应该是最高的(可以联想到二叉平衡树的效率)。但是快排不能保证选择一个数,就一定能将数组切分成为两半,所以它有自己特殊的处理。

利用 x 将数组分为三份
左子数组 = [小于 x 的部分] = [b, l)
根节点 = [等于 x 的部分] = [l, i)
右子数组 = [大于 x 的部分] = [i, e)

2. 根节点的信息处理

对于二叉树来说,根节点就是当前节点,也节点的处理也即是收集节点信息。

node
// 根节点信息处理
array.push(root.val);

而排序算法的"根节点"也就是选择的元素,并且排序算法会通过划分的子结构和选中的元素来进行排序处理也就是上面说的特殊处理;对于排序算法来说,"根节点"和划分子结构息息相关。

if (a[i] < x) {
    // 小于 x 的部分
} else if (a[i] === x) {
    // 等于 x 的部分
} else {
    // 大于 x 的部分
}

3. 将根节点的信息,传递给左右子树/左右子数组

二叉树前序遍历好说,通过递归的方式处理左右子树。

// 二叉树的前序遍历拿左右子树的信息
preOrder(root.left);
preOrder(root.right);

而排序算法需要分别对左子数组和右子数组进行排序,那么类似的对子数组的排序应该也只需要递归就可以了。

// 快速排序去拿左右子数组的信息
qsort(a, b, l);
qsort(a, i, e);

最后,不管是二叉树还是快速排序都要考虑一下边界:

二叉树的边界就是节点不能为空。

if (root === null) return null;

快速排序的边界就是:

if (b > e || b + 1 >= e) {
  return;
}

小结

对于二叉树来说,代码相对比较简单。

function preOrder(root, array = []) {
  // 边界处理
  if (root === null) return null;
  // 第一步:划分子结构,二叉树在结构上已经划分了子结构 root.left、root.right 可以直接通过树的子节点拿
  // 第二步:根节点的信息处理
  array.push(root.val)
  // 第三步:将根节点的信息,传递给左右子树/左右子数组(递归的方式)
  postOrder(root.left, array);
  postOrder(root.right, array);
}

对于快速排序来说,如何划分子结构?如何到达最优的效率?都是在写算法时需要注意的。

// 交换数组中两个元素的值 
function swap(A, i, j) {
  const t = A[i];
  A[i] = A[j];
  A[j] = t;
}
function qsort(a, begin, end) {
    // 边界情况
   if (b > e || b + 1 >= e) {
     return 
   }
 
 // 第一步:划分子结构
    const mid = b + ((end - begin) >> 1);
 // 第二步:获取根节点信息 x
 const x = a[mid];
 // 根据 x 将数组一分为三 【三路切分】
 let l = begin;
 let i = begin;
 let r = end - 1;
    while(i < r) {
        if (a[i] < x) {
            // 小于 x 的部分
            swap(a, l++, i++);
        } else if (a[i] === x) {
            // 等于 x 的部分
            i++;
        } else {
            // 大于 x 的部分
            swap(a, r--, i);
        }
    }

 // 第三步:将根节点的信息传递左右子子树
 qsort(a, b, l);
 qsort(a, i, e);
 
}
// 主函数,将数组nums排序 
function quickSort(nums) {
  if (nums == null)
    return;
  qsort(nums, 0, nums.length);
}

这里可以思考一下,为什么我们在节点排序处理是通过 swap 操作?它和时间复杂度和空间复杂度有什么关系?

while(i < r) {
    if (a[i] < x) {
        // 小于 x 的部分
        swap(a, l++, i++);
    } else if (a[i] === x) {
        // 等于 x 的部分
        i++;
    } else {
        // 大于 x 的部分
        swap(a, r--, i);
    }
}

总结

通过合并排序和快速排序,可以得出结论,数组其实是另外一种形式的二叉树,只不过有时候需要我们动态地把左/右子树给切分出来,不同的切分方式,可以解决不同的问题。大家也可以自己思考和尝试,看看还能不能发现更多排序的特点和巧妙用法,并且将它们总结下来。欢迎大家一起在评论区交流。

参考

来源:不爱吃猫的鱼er内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯