这是我参与「第三届青训营-后端场」笔记创作活动的的第4篇笔记.
一、简介
每个语言自带的排序算法:
python-timsort
C++-introsort
Rust-pdqsort
Go(<=1.18)-introsort,基于快排
Go(1.19)-pdqsort,在某些场景(sorted/reverse)中比以前快十倍,相比之前改变了pivot选择策略,fallback时机,是否针对不同pattern优化
二、经典排序算法
插入排序
将元素不断插入已经排序好的array中(遍历,每个元素不断向前交换,直到找到第一个比其小的元素)
时间复杂度O(n^2)
PartialInsertionSort:有限制次数的insert,如果次数到了还没排好就换方法
快速排序
分治思想,不断分割序列直到序列整体有序
选定一个轴点分割序列,分成比它大和比它小
时间复杂度O(n*logn)
堆排序
构造大顶堆,将根节点交换到最后一个位置,反复调整堆
时间复杂度O(n*logn)
Benchmark实际场景
根据序列元素排列情况划分
完全随机(random)
有序/逆序(sorted/reverse)
元素重复度高(mod8)
序列长度(16/128/1024)
random
短序列:insert
中长序列:quick
heap总体与最快算法差距不大
sorted
堆排序最快
结论
短序列和有序:insert
大部分情况;quick
任何情况:heap表现稳定
三、pdqsort
version1
不稳定的混合排序算法,结合各个算法的优势
短序列使用insert,其他情况使用quick,当quick表现不佳时用heap保证最坏情况时间复杂度仍然是O(n*logn)
短序列:12~32,常为24
如何得知quick表现不佳,需要使用heap?
轴点的位置与序列两端很接近(<length/8,说明要分很多次),这种情况的次数达到limit时(bits.len(length)),切换到堆排序
使其速度更快:尽量选择轴点为中位数->改进choose pivot
version2
pivot的选择
首个元素(最简单的方案):效果不好,sorted情况下性能很差
遍历数组,寻找中位数:遍历代价很高
根据序列长度的不同选择:
短序列(<=8),固定元素
中序列(<=50),采样三个元素,median of three
长序列(>50),采样九个元素,median of medians
采样方式可以探知序列当前状态
采样的都是逆序排列->可能逆序->翻转整个序列
采样的都是顺序->可能顺序->使用insert(实际PartialInsertionSort)
version3
优化重复元素多的情况
partitionEqual:两次partition生成的pivot相同,则进行了无效分割,用其将重复元素排列在一起,减少重复元素对于pivot选择的干扰
pivot选择策略表现不佳
limit-1,随机交换元素,避免一些极端情况和黑客攻击