c++ 快速排序_08 排序四:如何实现一个通用的排序算法?(附Java TimSort)

前面几篇讲过了几种常用的排序算法,这篇就是考虑如何实现一个通用的排序算法。

首先回顾一下排序算法的一些性能相关的信息,如图。

74de53332001595450c44f1da163152c.png

其中,线性排序的时间复杂度比较低,但是适用场景较为特殊,所以无法用于通用的排序算法。

如果是针对于小规模数据进行排序,可以选择时间复杂度为O(n^2)的排序算法;但如果对大规模的数据进行排序,还是时间复杂度O(nlogn)的更为高效。所以,为了兼顾任意规模数据的排序,一般选择时间复杂度为O(nlogn)的排序算法。

时间复杂度是O(nlogn)的排序算法不止一个,有前面说过的归并排序、快速排序,还有堆排序(等分享二叉树的时候再聊堆排序),这几个在编程语言中都有对应的排序函数实现。

以Java为例,其有基于快速排序的DualPivotQuicksort和基于归并排序的TimSort(以及ComparableTimSort,两者的主要区别在于是否使用自定义的比较器)。


下面聊一下TimSort的实现思路和代码。

TimSort的实现思路

这是一种基于归并排序和插入排序的混合排序算法,不过其对归并排序做了大量优化。

当待排序序列元素少于32个时,会使用binarySort,其基于二分查找实现了一个快速的插入排序算法。

当待排序序列元素大于等于32个时,就是真正的TimSort逻辑了。

  1. 选出minRun的大小,用于后续将待排序数组的分块。
  2. 找出初始的一组升序数据,countRunAndMakeAscending会找出一个runLen,代表找出的有序数据的长度。(如果寻找过程中发现的是一个降序数组,会进行reverse操作,保证升序)。
  3. 如果2中找到的runLen小于minRun,会利用binarySort对初始有序数组进行扩展,扩展后,保证仍有序。
  4. 进行入栈操作,分别存入有序数组起始下标、长度,便于后面的merge操作。
  5. 对栈中的多个数组进行merge操作。
  6. 重复2 ~ 5步骤,直到待排序数组中的数据排序完。
  7. 最终处理,如果此时仍有数组未merge,则进行merge,直到栈中数据都合并到一起。

代码

static 

merge操作时, 只对相邻块进行merge。

假设X、Y、Z为三个相邻块。

当栈中只有两个块时,if (X <= Y),将X和Y进行merge。

当栈中块数大于等于3时,if (X <= Y + Z)时,如果X < Z,merge X和Y,否则合并Y和Z。

直到栈中块数为1,或者同时满足X > Y + Z和Y > Z,本轮操作结束。

private 

当要合并时,由于要合并的两个序列已经有序,所以合并的时候,也有一处优化。假设两个序列是run1和run2,先用gallopRight找到run2首元素在run1中的位置(合并时可忽略run1中在此位置之前的元素),然后用gallopLeft找出run1尾元素在run2中的位置(合并时可忽略run2中在此位置之后的元素)。然后根据位置的大小关系执行mergeLo或者mergeHi。

private 

排序系列

淤白:05 排序一:冒泡、插入、选择​zhuanlan.zhihu.com
淤白:06 排序二:希尔、归并、快速​zhuanlan.zhihu.com
淤白:07 排序三:桶、计数、基数​zhuanlan.zhihu.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值