经典排序算法 | 青训营笔记

93 阅读2分钟

排序算法

经典排序算法

插入排序 InsertionSort

理解:将元素不断插入已经排好的数组中

  • 后续元素插入已经排好的有序序列中,不断交换,直到找到第一个比其小的元素 例如: 5 2 4 6 1 3
  1. 5 | 2 4 6 1 3
  2. 2 5 | 4 6 1 3
  3. 2 4 5 | 6 1 3
  4. 2 4 5 6 | 1 3
  5. 1 2 4 5 6 | 3
  6. 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

  • 对于短序列使用插入排序
  • 其他情况,使用快排保证整体性能
  • 当快排表现不佳时,使用堆排序保证最坏情况的时间复杂度
  1. 短序列的具体长度:12 ~ 32,在不同语言和场景中不同,在泛型版本根据测试选定24
  2. 快排表现不佳:当最终的pivot位置离两端很接近(距离小于length/8)时,判定表现不佳
  3. 切换到堆排序:当表现不佳的次数达到limit(即nits.Len(length))时,切换到堆排序

version2

改进快排的choose pivot, 让pdqsort更快
平衡寻找pivot所需要的开销和pivot带来的性能优化 => 寻找近似中位数

根据序列长度的不同,来决定选择策略
短序列 <=8 :采用固定元素
中序列 <=50 :采样三个元素
长序列 >50 :采样九个元素

final version

  • 采样pivot: 如果两次partition生成的pivot相同,即partition进行了无效分割,此时认为pivot的值为重复元素
  • 重复元素较多:检测到pivot和上次相同时,使用partitionEqual将重复元素排列在一起,减少重复元素对于pivot选择的干扰
  • pivot选择策略表现不佳时,随机交换元素