这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记
接下来我会对以下几方面进行总结:经典排序算法,最快的排序算法(Go 1.19)
经典排序算法
-
插入排序(Insertion Sortion) e.g.打牌
| Best | Avg | Worst |
|---|---|---|
| O(n) | O(n^2) | O(n^2) |
-
快速排序 (Quick sort)
过程:选定一个pivot,分成两列(一列比pivot大,一列比pivot小)
Best Avg Worst O(n*logn) O(n*logn) O(n^2) -
堆排序 (Heap Sort) (时间复杂度稳定)
过程: 大顶堆 root是最大值不断重复:
-
提出root
-
把末端数放入root
-
重构建大顶堆
| Best | Avg | Worst |
|---|---|---|
| O(n*logn) | O(n*logn) | O(n*logn) |
-
实际场景中:
| | 短序列(16) | 中序列(128) | 长序列(1024) | --- | ------- | --------------- ------------ | 完全随机情况 | 插入排序 | 快速排序| 快速排序 | sorted/reverse | 插入排序 | 插入排序 | 插入排序
结论:
-
所有短序列和元素有序情况下,插入排序性能最好
-
大部分情况,快速排序综合性能较好
-
在任何情况下,堆排序表现稳定
改进的算法
-
pdqsort(pattern-defeating-quicksort)
是一种不稳定的(对于相同的值会有不同的排序)混合排序算法应用在Go 1.19中。使得在不同条件下拥有不错性能
-
pdqsort v1
-
短序列(12~32)使用插入排序
-
其他情况使用快速排序保证整体性能
-
快速排序性能不佳(当最终的pivot的位置离两端很接近(距离<len/8)判定不佳,这种次数达到limit,切换到堆排序)时,使用堆排序维持住最坏情况(保证最差时间复杂度为O(n*logn)
- pdqsort v2 (改进pivot的选择)
-
方法一: 用首个元素作为pivot(若原本有序 达到最坏性能)
-
方法二: 遍历数组,寻找真正的中位数 (遍历比对代价高)
-
最好的方法: 寻找近似的中位数
短序列(<=8),选择固定元素
中序列(<=50),采样三个元素,选择三个元素的中位数作为中位数
长序列(>50), 采样9个元素, 选择中位数
(通过采样我们有探知序列的能力:如果采样的元素是逆序,序列可能是逆序,翻转!;采样的元素为顺序排序,使用插入排序!)
- pdqsort final version (优化重复元素很多) 如果两次partition生成的pivot相同,partition进行了无效分割,pivot值为重复元素
当发现上述情况,使用partitionEqual将重复元素排列在一起,减少对pivot选择干扰
**下面是pdqsort的时间复杂度 **
| Best | Avg | Worst |
|---|---|---|
| O(n) | O(n*logn) | O(n*logn) |