这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记
从零实现pdqsort(Pattern-Defeating-QuickSort)
Version1
结合数据结构与算法-排序算法(上)|青训营笔记一文中插入排序,快速排序以及堆排序三种排序算法的优点
- 对于短序列(小于一定长度),使用插入排序
- 其他情况,使用快速排序保证整体性能
- 当快速排序表现不佳时,使用堆排序来保证最坏情况下事件复杂度仍为O(nlogn)
短序列长度是多少
在go泛型版本中选定为24
如何得知快速排序表现不佳
使用快速排序每次会将数组分割成两个子数组,pivot的位置离序列两端很近(小于length/8)时,判定为表现不佳,当表现不佳次数超过一定阈值,切换至堆排序
总结
- 对于短序列(<=24),使用插入排序
- 其他情况,使用快速排序(选择首个元素为pivot)来保证整体性能
- 当快速排序表现不佳,使用堆排序来保证最坏情况下时间复杂度仍能为O(nlogn)
Version2
pivot的选择
- 使用首个元素作为pivot(最简单的方案) 实现简单,但数组在有序情况下,会达到最坏时间复杂度
- 遍历数组,寻找真正的中位数 遍历比对代价高,性能不好
优化pivot的选择
- 中序列(<=50),采样三个元素,取中位数,median of three
- 长序列(>50),采样九个元素,median of medians 通过pivot的采样方式,可以探知序列当前的状态 当采样元素都是逆序,推断序列可能逆序,翻转整个序列 当采样元素都是顺序,推断序列可能有序,使用插入排序
总结
- 升级pivot的选择策略(近似中位数)
- 发现序列可能逆序,翻转序列
- 发现序列可能有序,使用有限插入排序
final version
优化
- 重复元素较多的情况 检测到pivot和上次的pivot相同,使用partitionEqual将重复元素排列在一起,减少重复元素对pivot选择的干扰
- 当pivot选择策略表现不佳时,随机交换元素 避免一些极端情况使得快速排序总是表现不佳,以及一些黑客攻击情况