这是我参与「第三届青训营 -后端场」笔记创作活动的的第 4 篇笔记。
本篇笔记是对《数据结构与算法》课程的知识点总结,主要内容是生产环境使用的算法和数据结构。
课前问题
例子:抖音直播排行榜 规则:某个时间段内,直播间礼物 top10 房间获得奖励,在每个房间展示排行榜。
解决方案:
- 礼物数量存储在 Redis-zset 中,使用 skiplist 使得元素整体有序
- 使用 Redis 集群,避免单机压力过大,使用主从算法、分片算法
- 保证集群原信息的稳定,使用一致性算法
- 后端使用缓存算法(LRU)降低 Redis 压力,展示房间排行榜
go(<=1.18)introsort go(>1.18)pbqsort
经典排序算法
插入排序
将后面的元素不断插入已经排序好的 array 中
快速排序
分治思想,不断的分割序列直至整体有序
- 选定一个 pivot
- 使用 pivot 分割序列,将元素分成比 pivot 大的和比 pivot 小的两个序列
堆排序
利用堆的性质形成的排序算法
- 构造大顶堆
- 将根结点(最大元素)交换到最后一个位置,调整整个堆
实际场景 benchmark 时间复杂度
| best | avg | worst | |
|---|---|---|---|
| 插入排序 | O(n) | O(n^2) | O(n^2) |
| 快速排序 | O(nlogn) | O(nlogn) | O(n^2) |
| 堆排序 | O(nlogn) | O(nlogn) | O(nlogn) |
根据序列元素排列情况划分:
- 完全随机的情况(random)
- 有序/逆序的情况(sorted/reverse)
- 元素重复度较高的情况(mod8)
在此基础上,根据序列长度划分(16/128/1024):
- 短序列(len<=16):插入排序
- 中、长序列:快速排序
- 堆排序速度与最快算法差距不大
- 序列本身有序的情况下:插入排序
结论:
- 所有短序列和元素有序情况下,插入排序性能最好
- 在大部分的情况下,快速排序有较好的综合性能
- 几乎在任何情况下,堆排序的表现都比较稳定
如何设计更好的算法?
| best | avg | worst | ||||||
|---|---|---|---|---|---|---|---|---|
| 插入排序 | O(n) | O(n^2) | O(n^2) | |||||
| 快速排序 | O(nlogn) | O(nlogn) | O(n^2) | |||||
| 堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | pdqsort | O(n) | O(nlogn) | O(nlogn) |
pdqsort
pdqsort(pattern-defeating-quicksort) 是一种不稳定的混合排序算法,它的不同版本被应用在 C++ BOOST、Rust 以及 Go 1.19 中。它对常见的序列类型做了特殊的优化,使得在不同条件下都拥有不错的性能
version1: 结合三种排序的优点
- 短序列(len=24)使用 insertsort
- 其他情况,用 quicksort(选择首个元素作为 pivot) 保证整体性能
- 当 quicksort 表现不佳(limit==0)时,用 heapsort 保证最坏情况下的时间复杂度仍为 O(nlogn)
Q:如何得知表现不佳,以及何时切换到堆排序? A:当最终 pivot 的位置离序列两端很近时(d<length/8)判断为表现不佳;当这种情况的发生次数到的 limit(即 bits.Len(lenght)),切换到堆排序。
如何让pdqsort速度更快?
- 尽量使得 QuickSort 的 pivot 为序列的中位数->改进 choose pivot
- Partition 速度更快->改进 partition ,但是此优化在Go表现不好
version2:优化Pivot的选择
升级 pivot 选择策略,近似中位数。几次选择的中位数如果是降序的,那序列可能是逆序的,翻转序列;如果是可能升序的,使用插入排序
Pivot 的选择:根据序列长度不同,确定选择策略
- 短序列(<=8),选择固定元素
- 中序列(<=50),采样三个元素,median of three
- 长序列(>50),采样九个元素,median of medians
采样的元素都是逆序 → 序列可能逆序 → 翻转序列; 采样的元素都是顺序 → 序列可能有序 → 插入排序(partial insertion sort,即有限制次数的插入排序)
final version:
重复元素较多的情况(partitionEqual): 当检测到此时的 pivot 和上次相同时(发生在 leftSubArray),使用partitionEqual将重复元素排列在一起,减少重复元素对于pivot选择的干扰
当pivot选择策略表现不佳时,随机交换元素: 避免一些极端情况使得QuickSort总是表现不佳,以及一些黑客攻击情况