这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记 .
听过青训营老师讲述的go语言内置的排序算法,本文章对于go 1.19发行版内置算法
的由来做了一些总结和归纳:
首先,我们可以对序列元素的排序情况做几个大分类:
1.完全随机的情况
2.有序、逆序的情况
3.序列重复度高的情况
4.还需要根据长度划分
常用的几个排序算法:插入排序,快速排序,堆排序。哪种方法的时间复杂度最小呢? 从理论上分析:
插入排序:Best:O(n) Avg: O(n^2) worst: O(n^2)
快速排序:Best:O(nlogn) Avg:O(nlogn) worst:O(n^2)
堆排序: Best:O(nlogn) Avg:O(nlogn) worst: O(nlogn)
从时间复杂度方面分析,似乎堆排序的综合时间复杂度最小,那么最优的排序是堆排序吗? 在实际测试中,出现了以下结果:
benchmark-random(随机序列)
短序列中,插入排序最快
中序列中,快速排序最快
长序列中,快速排序最快
benchmark-sorted:(有序序列)
短序列中。插入排序最快
中序列中,插入排序最为快
长序列中,插入排序也是最快
由实际测试结果测试出:
在短序列和元素有序的情况下,插入排序的性能最好。在大部分情况下,快速排序有较好的综合性能。
在任何情况下,堆排序的表现最为稳定。
我们可以将这三种算法类比为三种常见的交通工具:
插入排序:单车
快速排序:汽车
堆排序:地铁
二、如何设计一个跟好的算法 如何将三种算法结合实现最优的算法?go 1.19的算法又是基于什么呢?
go 1.19的排序算法为pdqsort混合排序算法。 在pdqsort算法中,其实是对以上三种排序算法做了整合和取优:
pdqsort混合排序算法:
对于短序列使用插入排序,其他情况使用快速排序保证其稳定性,当快速排序表现不佳时,使用堆排序来保证最坏情况下时间复杂度为O(nlogn)
那么有以下问题:
1.短序列的具体长度是多少呢?
12~32,在不同语言和场景中使用,泛型版本为选定24
2.如何得知快速排序表现不佳,合适切换为堆排序?
可以从快排本身的行为来发现,当最终的pivot离序列两端很近时(距离小于length/8)判定其表现不佳,当这种情况的次数达到limit时,切换到堆排序。(limit==0,发现前几次排序效果不是很好,则limit-1,limit的初始值会根据长度计算出一个值)
由此诞生了:
pdqsort version1版本
但是version1版本有可以改进的地方,例如:
如何使快排更快呢?
1.选取pivot为中位数 -> 改进 choose pivot
2.pation速度更快 -> 改进partion,但此优化在go中并不好
因此有了pdqsort version2的出现: version1版本中使用首个元素缺点pivot,如果有序就会达到最坏情况 实现简单,但效果不好,在sorted下性能很差
解决方法:
遍历数组寻找中位数:遍历数组代价很高
寻找pivot所需要的开销,并平衡pivot的性能优化,寻找近似的中位数
根据序列长度的不同,来决定选择的策略
优化:
pivot选择:
短序列,选择固定元素
中序列,采样三个元素,选择中位数
长序列,采用九个元素,选择中位数
pivot采样方式也可以是我们有探知序列当前状态的能力:
如果采样的元素都是逆序,很可能序列已经逆序,所以先反转整个序列
如果采用元素都是顺序,序列恒可能有序,使用插入排序算法
(注意:这里的插入排序为partiallnsertionSort,即尝试三次的插入排序,若任然没有拍成一个顺序的序列,则放弃使用插入排序)
version2的优化总结:
1.选择了一个近似的中位数(采样三个点,先猜测,失败后考虑快排)
2.发现序列可能有逆序,先使用reverse
3.发现序列可能有序,使用有限插入排序
还有什么地方没有实现性能优化呢?
短序列情况:使用插入排序
极端情况:使用堆排序保证算法的可行性
完全随机的情况:跟好的pivot选择(version2的优化)
有序或逆序根据序列状态反转或者插入排序
对于元素重复度较高的情况,pivot采样的时候可以检测重复度吗?
不可行。因为采样数量有限,不一定能采样到相同的元素
解决防范:
如果两次partition生成的pivot相同,即partition进行了无效分割,此时认为pivot的值为重复元素
优化重复元素较多的情况:
重复元素较多:
当检测到此时pivot和上次相同时(发生在leftsubarray),使用partitionEqual将重复元素排列在一起,减少重复元素对于pivot选择的干扰
当选择策略表现不佳,随机交换元素:
可以防止一些黑客的攻击
pdqsort-final version(最终pdqsort算法的版本)
最终pdqsort的排序测试结果:
最好情况下o(n)
平均情况下o(nlogn)
最坏其概况下o(nlogn)
实际性能测试:
在有序和无需情况下提升了十倍
在其他情况下有10~50%的提升
总结:
高性能排序算法是如何设计的:
根据不同情况选择不同策略,取长补短
生产环境中的排序算法和课本上的排序算法有什么不同?
理论算法注重理论,例如时间空间复杂度等。生产算法中的算法需要对不同的实践场景,更加注重实际性能