排序算法
经典排序算法
插入排序 InsertionSort
理解:将元素不断插入已经排好的数组中
- 后续元素插入已经排好的有序序列中,不断交换,直到找到第一个比其小的元素 例如: 5 2 4 6 1 3
- 5 | 2 4 6 1 3
- 2 5 | 4 6 1 3
- 2 4 5 | 6 1 3
- 2 4 5 6 | 1 3
- 1 2 4 5 6 | 3
- 1 2 3 4 5 6 (√)
- 缺点:平均和最坏情况的时间复杂度为O(n^2)
- 优点:最好情况时间复杂度为O(n)
快速排序 QuickSort
理解:基于分治思想,不断分割序列直到序列整体有序
-
选定一个轴点pivot
-
使用pivot分割序列,分成元素比pivot大和元素比pivot小两个序列
-
缺点:最坏情况的时间复杂度为O(n^2)
-
优点:平均和最好情况时间复杂度为O(n*logn)
堆排序 HeapSort
-
构造大顶堆
-
将根节点即最大元素交换到最后一个位置,调整整个堆,反复操作
-
缺点:最好情况的时间复杂度为O(n*logn)
-
优点:最坏情况时间复杂度为O(n*logn)
实际场景 Benchmark
- 完全随机情况
| 16(短序列) | 128(中序列) | 1024(长序列) |
|---|---|---|
| 插入排序 | 快速排序 | 快速排序 |
- 有序/逆序
| 16(短序列) | 128(中序列) | 1024(长序列) |
|---|---|---|
| 插入排序 | 插入排序 | 插入排序 |
- 元素重复度较高
pdqsort (pattern-defeating-quicksort)
pdqsort是一种不稳定的混合排序算法
排序算法的稳定与否:在排序过程中交换相同元素的位置是不稳定的算法
version1
- 对于短序列使用插入排序
- 其他情况,使用快排保证整体性能
- 当快排表现不佳时,使用堆排序保证最坏情况的时间复杂度
- 短序列的具体长度:12 ~ 32,在不同语言和场景中不同,在泛型版本根据测试选定24
- 快排表现不佳:当最终的pivot位置离两端很接近(距离小于length/8)时,判定表现不佳
- 切换到堆排序:当表现不佳的次数达到limit(即nits.Len(length))时,切换到堆排序
version2
改进快排的choose pivot, 让pdqsort更快
平衡寻找pivot所需要的开销和pivot带来的性能优化 => 寻找近似中位数
根据序列长度的不同,来决定选择策略
短序列 <=8 :采用固定元素
中序列 <=50 :采样三个元素
长序列 >50 :采样九个元素
final version
- 采样pivot: 如果两次partition生成的pivot相同,即partition进行了无效分割,此时认为pivot的值为重复元素
- 重复元素较多:检测到pivot和上次相同时,使用partitionEqual将重复元素排列在一起,减少重复元素对于pivot选择的干扰
- pivot选择策略表现不佳时,随机交换元素