这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记。在学校期间,数据结构与算法课程少不了对排序算法的介绍,例如冒泡排序、选择排序、插入排序、快速排序、堆排序以及基数排序等等。 那么现在的编程语言的标准库中的排序算法是怎么样的呢?答案是一种自适应的快速排序算法。 学习了抖音青训营的数据结构与算法课程后,来学习学习pdqsort(pattern-defeating-quicksort)。
- 在所有基准测试中,pdqsort 从未明显慢于以前的算法
- 在常见模式中,pdqsort 通常更快(即在排序切片中快 10 倍)
下面来逐一介绍其原理:
- v1版本:结合三种排序方法的优点:
-
对于短序列(小于一定的长度)我们使用插入排序
-
其他情况,使用快速排序来保证整体性能
-
当快速排序表现不佳时,使用堆排序来保证最坏情况下时间复杂度为O(nlogn)
-
短序列的具体长度是多少呢? 12~32, 在不同语言和场景会不同,默认为24.
-
如何得知快速排序表现不佳,以及何时切换到堆排序?
当pivot的位置离序列两端很接近时(距离小于length/8) 判定其表现不佳,当这种情况的次数达到limit(即bits.Len(length))时, 切换到堆排序
下图展示了v1 的整个过程:
如何继续优化? 思考关于privot的选择
-
使用首个元素作为pivot(最简单)
实现简单,但是往往效果不好,例如在sorted情况下 性能很差
-
遍历数组,寻找真正的中位数
遍历代价很高,性能不好
于是寻找近似中位数: 据序列长度的不同,来决定选择策略
- 短序列 (<=8) , 选择固定元素
- 中序列 (<=50), 采样三个元素,选择三个元素中的中位数
- 长序列(>50),采样九个元素,中位数中的中位数。 pivot 的采样方式使得我们有探知序列当前状态的能力!
整合下来就诞生v2版本:
还能优化? 是的! 当检测到此时的pivot和上次相同时,认为重复元素较多,使用pritionEqual将元素排列在一起,减少重复元素对于privot的干扰。 最终版本的pdqsort是这样的: