排序小结

本文详细介绍了三种常见的排序算法:快速排序、归并排序和堆排序。快速排序采用分治策略,平均时间复杂度为O(nlogn),效率较高。归并排序同样基于分治,通过合并已排序的子序列逐步完成排序。堆排序利用堆的数据结构,原地排序且空间性能优秀。文章深入浅出地解析了每种排序算法的原理和实现步骤。
摘要由CSDN通过智能技术生成


重点就说三种排序吧,快速排序,归并排序还有堆排序。

快速排序

有关快速排序,江湖传言其效如名(狗头。虽然其平均时间与另外两个一致,但是大概率上它的耗时都优于平均情况。

快速排序方法是分而治之(divide and conquer,D&C)算法的一个很好的例子。分而治之是一种递归式的问题解决方法。既然是递归,也会涉及到基线条件,还有就是每次递归要逐渐缩小问题的规模,达到基线条件,才能顺利返回。如若不然,只会死循环或者遗漏信息。

那么快速排序既然从属分治,也就有基线条件,排序任务的基线条件是长度 <= 1 的数组不需要排序,可以直接返回;也同样会有缩小问题规模的,叫分区 partition。

快速排序的核心思路是,每一遍都会确定其中一个元素的最终位置。而且在确定某一个元素的最终位置的同时,它能够扭转很多逆序对。活儿干得非常有效率,这也是它能表现超出预期的原因之一吧。

每次能够确定一个元素的最终位置之后,你就可以把整个大问题划分成两个相对较小的问题,以此类推。

原地排序

所以需要两个函数分别承担上面的两部分工作:

(1)一个是确定某一个元素的最终位置,将该元素放好在这个位置上,然后返回这个位置索引。这一部分其实就是根据随机选择出来的那个元素基准值 pivot ,将整个序列隔开 partition,按照升序的话,比 pivot 小的扒拉到左边,比 pivot 大的扒拉到右边,最后把 pivot 蹲在“中间”返回下标索引 mid;

(2)另一个是根据前面所返回的索引 mid,将大问题分为两个子问题,对子问题递归进行定位某一个元素的最终位置,周而复始,直至结束。因为每次都会确定一个 mid,所以递归子问题的时候,子问题中不再包含 mid 这个位置,而是变成 (start, mid - 1) 和 (mid + 1, end)。

非原地排序

就很无脑了,在一个函数里面就能实现啦……,新建数组存放小于、等于、大于基数的数组,返回递归小于、等于、递归大于的 concat。

归并排序

归并排序早先一点都没看到呢,就在第一次面试赶上了逆序对,说来真的很羞愧(/(ㄒoㄒ)/~~)。

一个乱糟糟的序列如果很难排序的话,如果多给你创造点条件,排序两个已经排好序的序列呢?归并排序的核心就是解决这个问题。

那怎么创造那个“两个已经排好序的序列”条件呢?一个猛子扎进序列的最小单位去,长度为 1 的序列不需要排序吧?然后长度为 2 的序列是不是就可以看成“两个已经排好序的序列”呢?当有了两个长度为 2 的“已经排好序的序列”之后又怎么做呢?

想完上面的过程,归并排序就出来了。和快速排序有点类似,也分两部分工作,一部分就是工人,负责扒拉两个已经排好序的序列让它们按照顺序合二为一;另一部分就是包工头,负责调度“已经排序好的序列”给工人,让他们去扒拉合并排序。

这个过程里面有个临时空间的问题,就是工人每次扒拉好的序列要临时放一下,这小段工作做完了才能一起返回到原序列中归位。还有就是,归并排序的序列长度是任意的,所以可能分到子问题的时候不一定是相同的长度,mid 按照 (start + end) // 2,子问题是 (start, mid) 和 (mid+1, end)不能漏和重复。

堆排序

讲个笑话,我看完堆排序之后觉得已经明白了,然后用 python 的 heap 包做了几道题还觉得堆挺快挺好用 😃,就把堆顶的一个个往下拿,维护堆的问题全交给了包(捂脸,现在想想自己都要被自己逗得笑 yue 了。

因为堆可以直接用列表来很方便地操作,所以当给定一个有点长度的序列要求排序的时候,就直接在序列上来回交换元素操作就好了。那到底怎么交换元素啊?堆的特性决定了交换元素只存在与 parent 和 child 之间,所以你要想交换元素,就得先找父子关系。因为除了根节点以外,其他节点都是一定有爹但不一定有子,所以我们就先找当了爹的节点,也就是没有孩子的节点(当然就在序列尾部附近了)都可以跳过不进行操作,从序列尾部倒着遍历当爹的节点就可以。

然后,调整的时候,对于遍历到的每个爹(节点),根据最大堆或者最小堆立好家法,除了管好自己手下的一个孩子或者两个孩子之外,孙子重孙子都要看一眼要不要管(替换),如果不幸,可能要一直管到最小辈儿(序列尾部的叶节点,累啊),幸运的话可能只管手下两个孩子就可以了。当给定爹的索引之后,就可以一直去找孩子、孙子重孙到最尾端结束。

等你把序列从后到前把爹都遍历完了之后,此序列的最大堆/最小堆就建立好了。如果是最大堆的话,任务是升序排列,那么现在堆顶就是最大值,它应该在序列的最后位置,所以交换一下。交换完之后,临时在堆顶的那个可能不满足 root 的要求啊,你就得让他往下看看孩子节点视情况调整,这就又回到了上面一段的工作,只不过此时“尾端”的具体指向也变了,因为最后一个一定是最大值了,此时 root 对应的尾端应该前移一位,也就是此时的 root 插手不到稳稳在最后的前 root(前辈)。以此类推,一个个“最大值”慢慢有序地来到了尾部,最终序列也就排好了,整个过程就在序列原地进行,所以空间性能很好。

综上,建立堆的时候,从后向前遍历找当爹的节点,让它调整一下自己的家族成员位置,遍历完了也就建好堆了;具体说到调整家族成员位置的那部分,需要给定待排序序列、爹号以及这个爹最远的管辖范围。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值