这是我参与第三届青训营-后端场笔记创作活动的第4篇笔记
数据结构与算法
为什么要学习数据结构与算法
- 几乎存在于程序开发中的所有地方
经典排序算法
- 插入排序:将元素不断插入已经排好的array中,最好的时间复杂度为O(n),在有序的情况下发生,最坏为O(n^2),在倒序的情况下发生,avg O(n^2),缺点:时间复杂度很高
- 快排:基于分治的思想,不断的分割序列,分为比轴点大和小的两个序列,最好的时间复杂度为**O(nlogn)**选择的pivot是中位数,avg O(nlogn),最坏情况O(n^2)选择的pivot位于最开始的位置。
- 堆排序:最好的时间复杂度为O(nlogn),最坏的时间复杂度也为O(nlogn)。
根据序列元素排列情况进行划分:
- 完全随机的情况:插入排序在短序列中最快,其他情况下,快排最快,但是堆排序和其差距不是特别大
- 有序/逆序的情况:在完全有序的情况下,插入排序是最快的,快排是最慢的,堆排序表现比较稳定。
- 元素重复度较高的情况
在此基础上根据序列的长度进行划分(eg:随机情况)
- 短序列:16的情况,插入最快
- 中序列:128的情况,快排最快,堆排序和其差距并不大
- 长序列:1024的情况,快排最快,堆排序和其差距并不大 总结:在短序列和元素有序的情况下,插入排序的性能最好,在大部分情况下,快速排序有较好的综合性能,几乎在任何情况下,堆排序的表现都有比较稳定。
从零开始打造pdqsort
pdqsort是一种不稳定的混合排序算法。
version1
对于短序列,使用插入排序,在其他情况下使用快排,使用堆排序保证最坏情况下的使用。
何时切换到堆排序
当最终的pivot的位置离序列两端很接近的时候(距离小于length/8),判定其表现不佳,当这种情况的次数达到limit的时候(bits.len(length)),切换到堆排序。
改进
- 改进choosePivot
- 使用首个元素作为pivot的时候,有序的情况下性能很差;
- 遍历数组寻找真正的中位数,遍历比对代价很高,性能也不好
- 因此在这两个中间找一个平衡,寻找近似的中位数。 根据序列长度不同,来决定选择策略,短序列的情况选择一个固定的元素,中序列,采样三个元素,长序列,采样九个元素,在这几个元素中找到中位数作为pivot。
- 改进partition,在Go语言中表现不佳。
version2
pivot的采样方式让我们对序列当前状态有一定的掌握情况。如果也许有序,使用有限制的插入排序,如果超过了次数的时候,还是要使用快速排序。
优化总结:
- 升级pivot的选择策略
- 发现序列可能是逆序,使用翻转
- 若可能有序,使用有限的插入排序。
改进:对于重复度较高的情况下
如果两次partition生成的pivot相同,即partition进行了无效分隔,此时认为pivot的值为重复元素。使用partitionEqul将重复元素排列在一起,减少重复元素对于pivot选择的干扰