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

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

一、为什么要学习数据结构和算法

比如-抖音直播排行榜功用

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

解决方案

  • 礼物数量存储在Redis-zset中,运用skiplist 使得元素全体有序
  • 运用Redis集群,防止单机压力过大,运用主从算法、分片算法
  • 确保集群原信息的安稳,运用一-致性算法
  • 后端运用缓存算法(LRU)降低Redis压力,展示房间排行榜

问题

什么是最快的排序算法?

Python -timesort C++ -introsort Rust – pdqsort

go的排序算法有没有提升空间

Go(≤ 1.1.8)-introsort

从头完成了Go的排序算法,在某些场景中比之前的算法块 ~10倍,成为Go 1.19中的默认算法

二、经典排序

1. 刺进排序 Insertion Sort

将元素不断刺进到现已排好的Array中

  • 其实只有一个元素5,其自身是一个有序序列
  • 后续元素刺进有序序列中,即不断产生交流,直到找到第一个比其小的元素

最快时刻复杂度:O(N) 最差:O(N2) 空间复杂度:O(1)

缺陷 :

  • 最坏时刻复杂度高达 O(n2)

长处:

  • 最好状况下时刻复杂度 O(n)

2. 快速排序 Quick Sort

分治思想,不断切割序列直到序列全体有序

  • 选定一个pivot(轴点)
  • 运用pivot切割序列,分成元素比pivot大 和 元素比pivot小的两个序列

最快时刻复杂度:O(N*logN) 最差:O(N2) 空间复杂度:O(1)

缺陷 :

  • 最坏时刻复杂度高达 O(n2)

长处:

  • 最好状况下时刻复杂度 O(N*logN)

3. 堆排序 Heap Sort

运用对的性质形成的排序算法

  • 构造一个大顶堆
  • 将根节点(最大元素)交流到最后一个方位,调整整个堆,如此重复

最快时刻复杂度:O(NlogN) 最差:O(NlogN) 空间复杂度:O(logN)

缺陷 :

  • 最好时刻复杂度高达 O(N*logN)

长处:

  • 最坏状况下时刻复杂度 O(N*logN)

4. 经典算法理论形象

排序 Best Avg Worst
刺进排序 O(N) O(N2) O(N2)
快速排序 O(N*logN) O(N*logN) O(N2)
堆排序 O(N*logN) O(N*logN) O(N*logN)
  • 刺进排序均匀和最坏状况时刻复杂度都是O(n2),功能欠好
  • 快速排序全体功能处于中间层次
  • 堆排序功能安稳,“众生平等”

5. 实践场景

依据序列元素摆放状况区分

  • 完全随机的状况(random)
  • 有序/逆序的状况(sorted/reverse)
  • 元素重复度较高的状况(mod8)

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

状况 最快排序
短序列(长度为16) 刺进排序
中序列(长度为128) 快速排序
长序列(长度为1024) 快速排序
  • 刺进排序在短序列中速度最快
  • 快速排序在其他状况中速度最快
  • 堆排序速度于最快算法差距不大

假如序列自身就是有序的

状况 最快排序
短序列(长度为16) 刺进排序
中序列(长度为128) 刺进排序
长序列(长度为1024) 刺进排序
  • 刺进排序在现已有序的状况下速度最快

定论

  • 所有短序列和元素有序状况下,刺进排序功能最好
  • 在大部分的状况下,快速排序有较好的归纳功能
  • 简直在任何状况下,堆排序的体现都比较安稳

三、从零开始打造pdqsort

pdqsort (pattern- defeating-quicksort)

是一种不安稳的混合排序算法,它的不同版别被应用在C++ BOOST、Rust以及Go 1.19中。它对常见的序列类型做了特别的优化,使得在不同条件下都具有不错的功能

pdqsort – version1

结合三种排序方法的长处

  • 关于短序列(小于一定长度)咱们运用刺进排序
  • 其他状况,运用快速排序来确保全体功能
  • 当快速排序体现欠安时,运用堆排序来确保最坏状况下时刻复杂度依然为O(n*logn)

Q&A

  1. 1.短序列的具体长度是多少呢? 12 ~ 32, 在不同语言和场景中会有不同,在泛型版别依据测验选定24

  2. 怎么得知快速排序体现欠安,以及何时切换到堆排序?

    当最终pivot的方位离序列两端很挨近时(间隔小于length/8)断定其体现欠安,当这种状况的次数达到limit (即bits.Len(length))时,切换到堆排序

pdqqsort – version1

  • 关于短序列(≤24)咱们运用刺进排序
  • 其他状况,运用快速排序(挑选收割元素作为pivot)来确保全体功能
  • 当快速排序体现欠安时(limit==0),运用堆排序来确保最坏状况下时刻复杂度依然为O(n*logn)

问题:

怎么让pdqsort速度更快?

  • 尽量使得QuickSort的pivot为序列的中位数 -> 改进choose pivot
  • Partition速度更快 -> 改进partition,可是此优化在Go体现欠好,略

pdqsort – version2

考虑关于pivot的挑选

  • 运用首个元素作为pivot (最简单的方案 )

    完成简单,可是作用欠好,例如在sorted状况下功能很差

  • 遍历数组,寻觅真实的中位数

    遍历比对价值很高,功能欠好

寻觅近似

  • 依据序列长度不同没来决议挑选战略

  • 优化 – Pivot的挑选

    • 短序列(≤8)挑选固定元素
    • 中序列(≤50)采样三个元素,寻觅中位数
    • 长序列(>50)采样九个元素,寻觅中位数
  • pivot采样方法是咱们有探知不知道序列当时状况的能力!

    • 采样的元素都是逆序摆放 → 序列或许现已逆序 → 回转整个序列
    • 采样的元素都是顺序摆放 → 序列或许现已有序 → 运用刺进排序(刺进排序实践运用partiallnsertionSort,即有限次数的刺进排序)

Version1升级到version2优化总结

  • 升级pivot挑选战略(近似中位数)
  • 发现序列或许逆序,则翻转序列 → 应对reverse场景
  • 发现序列或许有序,运用有限刺进排序 → 应对sorted场景

还有什么场景咱们没有优化?

  • 短序列状况

    • 运用刺进排序(v1)
  • 极点状况

    • 运用堆排序确保算法的可行性(v1)
  • 完全随机的状况(random)

    • 更好的pivot挑选战略(v2)
  • 有序/逆序的状况(sorted/reverse)

    • 依据序列状况 翻转或者刺进排序(v2)
  • 元素重复度较高的状况(mod8) → ?

怎么优化重复元素许多的状况?

  • 采样pivot的时候检测重复度?

    不是很好因为采样数量有限,不一定能采样到相同元素

  • 解决方案:

    • 假如两次partition 生成的pivot相同,即partition进行了无效切割,此刻认为pivot的值为重复元素(相比上一种有更高的采样率)

pdqsort – final version

  • 优化 – 重复元素较多的状况(partitionEqual)

    • 当检测到此刻的pivot和上次相一起(产生在leftSubArray),运用partitionEqual将重复元素摆放在一起,削减重复元素关于pivot挑选的搅扰
  • 优化-当pivot挑选战略体现欠安时,随机交流元素

    • 防止一些极点状况使得QuickSort总是体现欠安,以及一些黑客进犯状况

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

排序 Best Avg Worst
InsertionSert O(N) O(N2) O(N2)
QuickSort O(N*logN) O(N*logN) O(N2)
HeapSort O(N*logN) O(N*logN) O(N*logN)
pdqSort O(N) O(N*logN) O(N*logN)

四、问题

高功能排序算法是怎么规划的

依据不同状况挑选不同战略,扬长避短

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

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

Go语言(<= 1.18)的排序算法是快速排序么?

实践一直是混合排序算法,主体是快速排序。Go ≤ 1.18时的算法也是根据快速排序,和pdqsort的区别在于fallback时机、pivot 挑选战略、是否有针对不同pattern优化等

\