Go 1.19 排序算法实践 - 课程笔记
课程引入
课程以不同高级语言的默认排序算法作为引入点
Python - timsort
C++ - introsort
Rust - pdqsort
然后过渡到Go的排序算法 有没有提升空间?
Go(<=1.18)-introsort
Go(>=1.19)-pdqSort
三大经典排序算法
-
Insertion Sort 插入排序
遍历元素,并将元素插入到 已经有序的 区间
-
Quick Sort 快速排序
分治思想,寻找一个pivot轴点,然后将元素分为 pivot为依据的 一大一小 两个序列
-
Heap Sort 堆排序
利用堆的 堆序性。 新加入的元素放入 最后一个位置,然后上筛。
三种算法的 三种情况 的对比
最好情况 平均情况 最坏情况
结论
- 插入排序平均和最坏情况时间复杂度都是 ,性能不好
- 快速排序整体性能处于 中间层次
- 堆排序性能稳定, “众生平等”
提出疑问
能不能有这么一个算法, 能够结合三者的优点,
达到对于三种情况 都有着最优的性能?
从零开始打造 pdqSort
(pattern-defeating-quicksort)
v1
流程图如下:
策略:
-
对于短序列(<= 24)使用插入排序
-
其他情况,使用快速排序(使用首个元素作为pivot)来保证整体性能
-
当快速排序表现不佳(limit == 0),使用堆排序来保证 最坏情况 的时间复杂度
limit是快排的表现效果,最开始的值根据序列的长度而定,每使用一次快排就 -1,到 limit = 0 时,说明使用了多次快排 且表现不佳
==需要改进的问题==: 改进choose pivot 因为默认是选取第一个元素,这肯定不妥。
v2 改进choose pivot
流程图如下:
不使用首个元素作为 pivot (特别在有序情况下 性能不好)
遍历比对的代价高,性能不好。
所要做的:
寻找pivot所需要的开销 <= ==平衡== => pivot带来的性能优化
==寻找近似中位数==
根据序列长度的不同,来决定选择策略
选一个随机的 是否可行?
不行,一般来说在实际的表现不是 最优的
- 生成一个伪随机数,需要性能开销
- 随机的pivot不能真正代表实际情况
优化-Pivot 的选择
- 短序列(<=8),选择固定元素
- 中序列(<=50),采样三个元素, median of tree ,选中位数。
- 长序列(>50),采样九个元素, median of medians
pivot的采样方式使得我们 有探知序列当前状态的能力!
采样的元素都是逆序排列 => 序列==可能==已经逆序 =》 翻转整个序列
采样的元素都是顺序排列 => 序列==可能==已经有序 =》 使用插入排序
注:插入排序 实际使用 partialInesertionSort, 即有限制次数的插入排序
还有什么场景没有优化
已经优化的
- 短序列情况
- 用插入排序(v1)
- 极端情况
- 使用堆排序保证算法的可行性(v1)
- 完全随机的情况(random)
- 更好的pivot选择策略(v2)
- 有序/逆序的情况(sorted/reverse)
- 根据序列状态翻转或者插入排序(v2)
未优化:
重复度较高
final version
流程图如下:
对比
end
高性能的排序算法是如何设计的?
根据不同情况选择不同策略,取长补短
生产环境中使用的的排序算法和课本上的排序算法有什么区别?
理论算法注重理论性能,例如时间、空间复杂度等。
生产环境中的算法需要面对不同的实践场景,更加注重实践性能
Go语言(<=1.18)的排序算法是快速排序么?
实际一直是混合排序算法,主体是快速排序。
Go <= 1.18时的算法也是基于快速排序,和pdqsort的区别在于fallback时机、pivot选择策略、是否有针对不同pattern优化等