pdqsort笔记 | 青训营笔记

113 阅读1分钟

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

1.为什么要学习数据结构和算法

规则:某个时间段内,直播间礼物数TOP10房间获得奖励,需要在每个房间展示排行榜

  • 礼物数量存储在Redis-zset中,使用skiplist使得元素整体有序
  • 使用Redis集群来避免单机压力过大,使用主从算法、分片算法
  • 保证集群原信息的稳定,使用一致性算法
  • 后端使用缓存算法(LRU)缓解Redis压力,展示房间排行榜

数据结构和算法几乎存在于程序开发中的所有地方

什么是最快的排序算法?

Python-timsort C+ + -introsort Rust-pdqsort

Go的排序算法有没有提升空间?

Go(<=1.18)-introsort

  • Go 1.19的默认排序算法pdqsort是如何设计的?
  • 生产环境中使用的排序算法和课本上的排序算法有什么区别?
  • Go语言的排序算法是快速排序吗?

2.经典排序算法

1.插入排序

思想:从一个元素开始不断插入新的元素,每次插入通过往前比大小确认自己在当前序列的位置来保证做之后的插入之前都是一个有序的序列。

优点

  • 最坏情况的时间复杂度高达O(n^2) 缺点
  • 平均情况的时间复杂度为O(n)

2.快速排序

思想:通过一个基准值(一般是队头或者队尾)去把数分成比基准值小的左队和比基准值大的右队,每一次比较队头和队尾(这两个里面其中有一个是基准值)来分配左队还是右队。

优点

  • 最坏情况的时间复杂度高达O(n^2) 缺点
  • 平均情况的时间复杂度为O(n*logn)

3.堆排序

思想:1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。

优点

  • 最好情况的时间复杂度高达O(n*logn) 缺点
  • 最坏情况的时间复杂度为O(n*logn)

Benchmark

实际应用场景下我们会先分析序列元素排列情况,有以下几种

  • 完全随机,数和数之间没有规律
  • 有序/逆序的情况,已经排好序的
  • 元素重复度较高的情况,mod8

在此基础上,还需要根据序列长度的划分(16/128/1024)

image-20220526003356368.png

3.从零开始打造pdqsort

version1:

对于短序列(小于某一长度)我们使用插入排序 其他情况,使用快速排序来保证整体性能 当快速排序表现不佳时,我们使用堆排序来保证最坏情况也是nlogn

image-20220526003536948.png

version2:

image.png

Version1升级到version2优化总结:

  • 升级pivot选择策略(近似中位数)
  • 发现序列可能逆序,则翻转序列->应对reverse场景
  • 发现序列可能有序,使用有限插入排序->应对sorted场景

课程中问题

1.生产环境中使用的排序算法和课本上的排序算法有什么区别?

理论算法注重理论性能,例如时间、空间复杂度等。生产环境中的算法需要面对变化的不同的实际场景,更加注重实践性能。

2.Go语言的排序算法是快速排序吗?

在go<=1.18之前主体其实都是混合排序算法,不过主体是快速排序,和pdqsort的区别在于fallback时机、快排基准值的选择策略、是否有对不同的pattern优化。