.NET 6 优先队列 PriorityQueue 实现分析
在最近发布的 .NET 6 中,包含了一个新的数据结构,优先队列 PriorityQueue, 实际上这个数据结构在隔壁 Java中已经存在了很多年了, 那优先队列是怎么实现的呢? 让我们来一探究竟吧。
时间复杂度
因为接下来会分析时间复杂度, 这里先贴一张几种时间复杂度的对比图,从低阶到高阶有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2 )。

什么是优先队列
首先,队列大家都知道, 是一个非常基础的数据结构, 它的特点是先进先出(FIFO)。

而优先队列却不一定是先进先出,因为每个元素都有一个权重值, 代表着元素出队的优先级。

队列可以用数组和链表实现, 简单、高效, 这样入队和出队的时间复杂度都是 O(1)。

优先队列能不能使用上面的方法呢? 也可以, 但是每次新元素入队后, 需要和队列内的元素进行遍历和大小对比, 然后插入到合适的位置, 让整个序列保持从大到小或者从小到大,这样入队的时间复杂度变成 O(n), 而出队复杂度不变, 还是 O(1)。O(n) 代表入队的时间是线性增长的, 效率较低, 有没有更高效的方法呢?
堆 Heap
堆这种数据结构的应用场景非常多,最经典的莫过于堆排序了, 堆排序是一种原地的、时间复杂度为 O(nlog n) 的排序算法,另外,堆也很适合用来做优先队列。
堆和树的结构其实是相似的, 堆有二叉堆, d-ary 堆, 2-3 堆, 斐波那契堆等等, 堆有一个特点就是每个父节点都大于等于它的儿子节点, 这种是大顶堆, 或者每个父节点都小于等于它的儿子节点, 这种是小顶堆,另外堆的儿子不分左右, 其中 java 中的 PriorityQueue 就是用二叉小顶堆实现的。

上面就是二叉堆, 而 .NET 6 中的 PriorityQueue 是由 d-ary 堆实现的, 而 d 表示父节点有几个儿子节点, .NET 6 中指定这个值为4,并且是小顶堆,也就是 “四叉小顶堆"。

四叉堆比二叉堆更快,可以参考下面链接的论文
A Back-to-Basics Empirical Study of Priority Queues
那么如何在代码中实现呢?其实可以用数组存储堆, 我们可以通过”广度优先遍历“ 的方法, 把堆的节点映射到一个数组中,如下

另外,堆和数组之间还有下面的关系
堆的顶点就是数组的第一个元素,也是最小的元素。
通过子节点的下标,就可以通过公式计算出父节点的下标, 公式为
P = (C - 1) / 4
其中 P = 父节点的下标, C = 子节点的下标

现在优先队列的数据结构确定了, 接下来看元素的入队和出队。
入队 Enqueue
使用堆来实现优先队列,入队操作2步完成, 非常简单!
添加新节点到末尾
通过上面的公式
P = (C - 1) / 4, 新的子节点和父节点进行大小对比,如果子节点比较小,那么就和父节点交换,重复这个过程,直到子节点大于或等于父节点,或者子节点变成堆顶,堆化完成, 这个交换过程是从下往上的, 入队的时间复杂度是 O(log n)。

出队 Dequeue
出队,就是每次取队列内最小的元素,基小顶堆结构,其实只需要取堆顶的元素即可,对应数组的第1个元素 array[0]。
你会发现,当取出堆顶元素以后,小顶堆的顶已经空了, 为了保持堆的结构,我们需要重新堆化。
和上面的入队 Enqueue 的逻辑有异曲同工之妙, 我们可以取堆的最后一个元素,把它放到堆顶, 然后父节点去和4个儿子节点比大小,如果比儿子节点大,就交换, 重复这个过程,直到父节点比4个儿子节点都大, 或者到达堆的最后一层,堆化完成,这个交换过程是从上往下的,出队的时间复杂度同样是 O(log n)。
另外,如果多个儿子节点都比父节点小,那父节点和最小的子节点交换。

扩容和收缩机制
优先队列是用数组实现的四叉小顶堆, 那么就存在数组的扩容和收缩的情况
扩容:最小为4,数组满的时候会扩大为当前容量的2倍。
收缩:数组不会自动收缩,不过可以手动调用 TrimExcess() 方法, 当空余的空间大于10% 的时候, 数组的长度会收缩到当前队列元素的数量。
总结
本文主要介绍了 .NET 6 新增的数据结构优先队列,感兴趣的也可以看一下 PriorityQueue 的源码, 其实就是基于堆这种结构实现的,也展示了入队和出队的堆结构的变化过程,另外需要注意的是,堆这种结构不是稳定的,因为在排序的过程,存在将堆的最后一个节点跟堆顶节点互换的操作,所以以相同优先级入队的元素并不能保证以相同的顺序出队。
参考
System/Collections/Generic/PriorityQueue.cs
https://github.com/dotnet/runtime/issues/14032
https://en.wikipedia.org/wiki/D-ary_heap
A Back-to-Basics Empirical Study of Priority Queues

.NET 6 优先队列 PriorityQueue 实现分析的更多相关文章
- 【Java源码】集合类-优先队列PriorityQueue
一.类继承关系 public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serial ...
- [Swift]实现优先队列PriorityQueue
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...
- Java的优先队列PriorityQueue详解
一.优先队列概述 优先队列PriorityQueue是Queue接口的实现,可以对其中元素进行排序, 可以放基本数据类型的包装类(如:Integer,Long等)或自定义的类 对于基本数据类型的包装器 ...
- Java优先队列PriorityQueue的各种打开方式以及一些你不知道的细节
目录 Java优先队列PriorityQueue的各种打开方式以及一些你不知道的细节 优先队列的默认用法-从小到大排序 对String类用优先队列从大到小排序 通过自定义比较器对自定义的类进行从小到大 ...
- 优先队列PriorityQueue实现 大小根堆 解决top k 问题
转载:https://www.cnblogs.com/lifegoesonitself/p/3391741.html PriorityQueue是从JDK1.5开始提供的新的数据结构接口,它是一种基于 ...
- 优先队列(priorityqueue)
队列是先进先出的线性表,顾名思义,优先队列则是元素有优先级的队列,出列的顺序由元素的优先级决定.从优先队列中删除元素是根据优先权的高低次序,而不是元素进入队列的次序.优先队列的典型应用是机器调度等. ...
- [Swift]优先队列PriorityQueue(自定义数据结构)
优先队列[priority queue] 普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除. 优先队列特点:在优先队列中,元素被赋予优先级. 当访问元素时,具有最高优先级的元素最先 ...
- Python 标准库 —— 队列(Queue,优先队列 PriorityQueue)
优先队列,有别于普通队列的先入先出(虽然字面上还是队列,但其实无论从含义还是实现上,和普通队列都有很大的区别),也有别于栈的先入后出.在实现上,它一般通过堆这一数据结构,而堆其实是一种完全二叉树,它会 ...
- PriorityQueue原理分析——基于源码
在业务场景中,处理一个任务队列,可能需要依照某种优先级顺序,这时,Java中的PriorityQueue(优先队列)便可以派上用场.优先队列的原理与堆排序密不可分,可以参考我之前的一篇博客: 堆排序总 ...
随机推荐
- Sql server 删除重复记录的SQL语句
原文地址:https://www.cnblogs.com/luohoufu/archive/2008/06/05/1214286.html 在项目开发过程中有个模块日清表页面数据重复,当时那个页面的数 ...
- Qt5加载SVG格式的图片并更颜色
QIcon MainWindow::qiconFromSvg(QString svg_path, QString color) { QPixmap img(svg_path); QPainter qp ...
- 洛谷 P3267 - [JLOI2016/SHOI2016]侦察守卫(树形 dp)
洛谷题面传送门 经典题一道,下次就称这种"覆盖距离不超过 xxx 的树形 dp"为<侦察守卫模型> 我们考虑树形 \(dp\),设 \(f_{x,j}\) 表示钦定了 ...
- LG 11 月 月赛 II T4
LG 11 月 月赛 II T4 看到膜数和 $ 10^5 $ 以及 $ n^2 $ 的部分分想到很可能是 NTT 于是开始推式子 首先看到式子可以化作, 如果 \(k = 0\) , $ f(l , ...
- 解决install_github安装R包时无法打开(cannot open)URL?
最近安装github上的R包,经常显示Error in utils::download.file(url, path, method = method, quiet = quiet, : 无法打开UR ...
- Java 读取txt文件生成Word文档
本文将以Java程序代码为例介绍如何读取txt文件中的内容,生成Word文档.在编辑代码前,可参考如下代码环境进行配置: IntelliJ IDEA Free Spire.Doc for Java T ...
- C/C++ Qt 数据库QSql增删改查组件应用
Qt SQL模块是Qt中用来操作数据库的类,该类封装了各种SQL数据库接口,可以很方便的链接并使用,数据的获取也使用了典型的Model/View结构,通过MV结构映射我们可以实现数据与通用组件的灵活绑 ...
- Oracle数据库导入与导出方法简述
说明: 1.数据库数据导入导出方法有多种,可以通过exp/imp命令导入导出,也可以用第三方工具导出,如:PLSQL 2.如果熟悉命令,建议用exp/imp命令导入导出,避免第三方工具版本差异引起的问 ...
- NSURLSessionDownloadTask实现大文件下载
- 4.1 涉及知识点(1)使用NSURLSession和NSURLSessionDownload可以很方便的实现文件下载操作 第一个参数:要下载文件的url路径 第二个参数:当接收完服务器返回的数据 ...
- matplotlib画直线图的基本用法
一 figure使用 1 import numpy as np 2 import matplotlib.pyplot as plt 3 4 # 从-3到中取50个数 5 x = np.linspac ...