数据结构与算法|青训营笔记

111 阅读3分钟

这是我参与第三届青训营-后端场笔记创作活动的第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)

根据序列元素排列情况进行划分:

  1. 完全随机的情况:插入排序在短序列中最快,其他情况下,快排最快,但是堆排序和其差距不是特别大
  2. 有序/逆序的情况:在完全有序的情况下,插入排序是最快的,快排是最慢的,堆排序表现比较稳定。
  3. 元素重复度较高的情况

在此基础上根据序列的长度进行划分(eg:随机情况)

  1. 短序列:16的情况,插入最快
  2. 中序列:128的情况,快排最快,堆排序和其差距并不大
  3. 长序列: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选择的干扰

final version