新型排序算法pdqsort | 青训营笔记

97 阅读5分钟

新型排序算法pdqsort

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

如:展示抖音直播间礼物数TOP10,需要在每个直播间展示排行榜

其中礼物数量储存在Redis-zset中,使用skiplist使得元素整体有序

在redis集群中,避免单机压力过大,使用主从算法、分片算法,

为了保证集群信息的稳定,使用一致性算法

后端使用缓存算法LRU降低Redis压力,展示房间排行榜

经典排序算法

插入排序:

它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入, 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列, 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

1.将元素不断插入已经排序好的array中,起始只有一个元素5,既是正序也是逆序其,本身是一个有序序列

2.后续元素插入有序序列中,即不断交换,直到找到第一个比其小的元素

  • 缺点:时间复杂度很高,平均和最坏情况的时间复杂度是n的平方
  • 优点:最好情况时间复杂度为n

快速排序:从数列中挑出一个元素,称为 "基准"(pivot);重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序

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

1.选定一个pivot轴点

2.使用pivot分割序列,分成元素比pivot大和元素比pivot小俩个序列

  • 缺点:最坏情况的时间复杂度高达n的平方
  • 优点:平均情况的时间复杂度n*logn

堆排序:堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法

  • 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  • 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

堆排序的平均时间复杂度为 Ο(nlogn)。

  • 创建一个堆 H[0……n-1];
  • 把堆首(最大值)和堆尾互换;
  • 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
  • 重复步骤 2,直到堆的尺寸为 1。

1.构造一个大顶堆,

2.将根节点(最大元素)交换到最后一个位置,调整整个堆,如此反复

  • 缺点:最好情况的时间复杂度高达n*logn

在序列长度的划分为16/128/1024下

1.在完全随机的情况下(random)

  • 插入排序在短序列中速度最快
  • 快速排序在其他情况中速度最快
  • 堆排序速度与最快算法差距不大

2.在有序/逆序的情况下(sorted/reverse)

插入排序在序列已经有序的情况下排序最快

pdqsort简介(pattern-defeating-quicksort)

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

pdqsort - version1

1.对于短序列泛型版本长度(<=24)我们使用插入排序

2.其他情况,使用快速排序(选择首个元素作为pivot))来保证整体性能

3.当快速排序表现不佳时即当最终pivot的位置离序列两端很接近时距离小于length/8,并且当这种情况次数达到limit即 bits.Len(length)(limit==0?) ,使用堆排序来保证最坏情况下时间复杂度仍然为O(nlogn)

如何让pdqsort速度更快?

1.尽量使得QuickSort的pivot为序列的中位数->改进choose pivot

2.Partition速度更快->改进partition

pdqsort - version2

寻找近似中位数,根据序列长度的不同,来决定选择策略

优化Pivot的选择

短序列(<=8),选择固定元素 中序列(<=50),采样三个元素,median of three 长序列(>50).采样九个元素,median of medians

Pivot的采样方式使得我们有探知序列当前状态的能力

采样的元素都是逆序排列 -> 序列可能已经逆序 -> 翻转整个序列

采样的元素都是顺序排列 -> 序列可能已经有序 -> 使用插入排序

注:插入排序实际使用partiallnsertionSort,即有限制次数的插入排序

Version1升级到version2优化总结

升级 pivot选择策略(近似中位数)

发现序列可能逆序,则翻转序列->应对reverse场景

发现序列可能有序,使用有限插入排序->应对sorted场景

pdqsort - final version

如何优化重复元素很多的情况?

解决方案︰如果两次partition生成的pivot相同,即partition进行了无效分割,此时认为pivot的值为重复元素。

优化 - 重复元素较多的情况(partitionEqual)

当检测到此时的 pivot和上次相同时(发生在leftSubArray),使用partitionEqual将重复元素排列在一起,减少重复元素对于pivot选择的干扰。

优化-当pivot选择策略表现不佳时,随机交换元素

避免一些极端情况使得QuickSort总是表现不佳,以及一些黑客攻击情况。

总结

pdqsort 的主要改进在于,其对 common cases (常见的情况)做了特殊优化。因此在这些情况下性能超越了之前算法,并且相比C++内联排序introsort 在随机序列的排序性能基本保持了一致。例如当序列本身有序、完全逆序、基本有序这些情况下都超越了大部分算法。其主要的思想是,不断判定目前的序列情况,然后使用不同的方式和路径达到最优解。