排序算法优化pqdsort| 青训营笔记

169 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记。

一.引入:三种经典排序算法

1.快速排序算法: 采用分治思想,每次选择一个pivot(锚点)对array进行划分,使得这个array中位于pivot左边的元素都小于pivot,右边的都大于pivot,再对pivot划分成的两个sub-arrays进行相同操作至整体有序。

2.堆排序算法: 利用二叉堆进行排序的选择排序算法。首先将无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆),接着将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。其最好,最坏,平均时间复杂度都为O(n* logn)。

3.插入排序算法: 每一次将一个待排序元素插入到前方已经排序好的序列中直至插入所有元素。在短序列和整体有序的情况下有良好的性能表现。

快排复杂度.png

二.混合排序算法 pqdsort

pqdsort(pattern-defeating-quicksort)是一种混合排序算法,对常见的序列类型做了特殊的优化,使得在不同条件下都拥有不错的性能。其理想情况下的时间复杂度为 O(n),最坏情况下的时间复杂度为 O(n* logn),不需要额外的空间。

其整体设计图如图所示:

快排思路.png

对于短序列数组使用插入排序进行排序后直接返回。在Go语言的性能测试下将最大插入序列长度选定为了24。

对于最坏情况即改进后的quicksort效果不佳的情况下(表现为limit==0,limit初始值由待排序序列长度计算而来,每次快排效果不佳即选择的pivot距离两端小于length/8时,limit减一)将排序算法换为时间复杂度较稳定的堆排序算法保证整体性能。

对于正常的输入情况则使用改进的quicksort进行排序。

快排优化上pivot的选择策略采取近似中位数的方法。根据序列长度不同采用不同的采样选择策略,对于中序列(<=50)采样3个元素即 L/4* 1, L/4* 2, L/4* 3 的中间值,对于长序列(>50)采样9个元素。

pivot的选择也让我们有了探知序列当前状态的能力,如采样的多个元素均有序则数组很可能已经有序,若都局部逆序则可能数组完全逆序。因此当发现序列可能已经逆序的情况采取翻转整个序列的方法优化。当序列可能有序时使用有限次的插入排序应对,通过增加尝试次数限制避免猜测错误导致消耗大量时间。如果达到尝试次数时 array 仍未有序,则退出。如果在尝试次数之前发现所有元素有序,则可以直接返回。

对于重复元素较多的情况,当检测到此时的pivot和上次相同时,使用partionEqual将重复元素排列在一起,减少重复元素对于pivot选择的干扰。

当pivot选择策略表现不佳时即最终pivot的位置离array两端之一很近时,随机交换元素避免一些极端情况和可能的黑客攻击情况。

三.参考文章:

打造 Go 语言最快的排序算法 (qq.com)