优先级队列的学习 - 教程

news/2025/11/13 21:53:04/文章来源:https://www.cnblogs.com/gccbuaa/p/19219673

优先级队列

队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,这种场景下,使用队列显然不合适。比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。
在这种情况下,数据结构应该提供两个最基本的操作**,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列**(PriorityQueue)。
在这里插入图片描述

优先级队列的模拟实现

JDK1.8 中的 PriorityQueue 底层使用了堆这种数据结构,而堆实际就是在完全二叉树的基础上进行了一些调整。
如果有一个关键码的集合 K={k₀,k₁,k₂,…,kₙ₋₁},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Kᵢ≤K₂ᵢ₊₁ 且 Kᵢ≤K₂ᵢ₊₂(或 Kᵢ≥K₂ᵢ₊₁ 且 Kᵢ≥K₂ᵢ₊₂)(i=0,1,2…),则称为小堆(或大堆)。
其中,根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
这种定义体现了堆的核心特性:堆是完全二叉树的顺序存储结构,且父节点与子节点之间存在严格的大小关系(小堆中父节点小于等于子节点,大堆中父节点大于等于子节点),这也是堆能高效支持优先级队列操作的基础。

堆的性质:

• 堆中某个节点的值总是不⼤于或不⼩于其⽗节点的值;
• 堆总是⼀棵完全⼆叉树,分为大根堆小根堆

堆的存储方式

从堆的概念可知,堆是一棵完全二叉树,因此可以按层序的规则采用顺序的方式来高效存储。
左边这一张图适合用数组去存,因为他是完全二叉树,每一个都有位置
右边这一张图不适合可以用数组去存,为了能还原二叉树,空间中必须要存储空字节点,一些空间是null也被占用,导致空间来利用率比较低。
将元素存储到数组中后:

  • 如果 i 为 0,则 i 表示的节点为根节点,否则 i 节点的双亲节点为 (i-1)/2
  • 如果 2i+1 小于节点个数,则节点 i 的左孩子下标为 2i+1(即左孩子存在,不超出数组范围)。,否则没有左孩子
  • 如果 2i+2 小于节点个数,则节点 i 的右孩子下标为 2i+2,(即右孩子存在,不超出数组范围)。否则没有右孩子

堆的创建

堆向下调整
向下调整过程(以小堆为例):
让 parent 标记需要调整的节点,child 标记 parent 的左孩子(注意:parent 如果有孩子,一定先是有左孩子)。
如果 parent 的左孩子存在,即:child < size,进行以下操作,直到 parent 的左孩子不存在:
检查 parent 的右孩子是否存在,若存在则找到左右孩子中最小的孩子,让 child 标记这个最小的孩子。
将 parent 与较小的孩子 child 比较:
若 parent 小于较小的孩子 child,调整结束。
否则:交换 parent 与较小的孩子 child。交换完成后,parent 中较大的元素向下移动,可能导致子树不满足堆的性质,因此需要继续向下调整,即 parent = child;child = parent * 2 + 1;然后重复步骤 2。

在这里插入图片描述
usedSize 表示实际有效节点数

  • len 是数组的总容量(数组长度),即数组最多能存储的节点数。
  • usedSize 是当前已存储的有效节点数量(0 ≤ usedSize ≤ len)。
  • usedSize - 1 表示最后一个有效节点的索引(因为数组下标从 0 开始)。
    usedSize - 1 < len(因为 usedSize ≤ len,当 usedSize = len 时,usedSize - 1 = len - 1,等于数组最大索引)。
  • 当然某些场景中,usedSize 可能直接表示数组长度(即所有数组元素都是有效节点),此时 usedSize = len。
    createHeap 方法:堆的构建入口
for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {
siftDown(parent, usedSize);
}
  • 从最后一棵子树的根节点开始(通过 (usedSize-1-1)/2 计算父节点下标),从下往上、从右往左遍历每一棵子树。
  • 对每棵子树调用 siftDown 方法,逐步将整个数组调整为大根堆。

siftDown 方法:向下调整逻辑(以大堆为例子)

public void siftDown(int parent, int usedSize){
int child = 2 * parent + 1;
//确保child(子节点下标)在数组的有效范围内
while (child < usedSize) {
//elem[child + 1]这里可能会产生child + 1 的空
//             if(elem[child] < elem[child + 1]){
//                 child++;
//             }
//判断右子节点是否存在且更大,如果是child不就往后移动
if(elem[child] < elem[child + 1] && child + 1 < usedSize){
//先判断右子节点是否存在(child+1 < size);
//若存在,比较左右子节点的值,把较小的子节点下标用child标记
child++;
}
//代码走到这里 表示 迟来的下标一定是醉倒的孩子下标
}
}
}
  • child = 2 * parent + 1:根据父节点下标,计算左孩子下标(堆的顺序存储特性)。
  • 条件判断 if(elem[child] < elem[child + 1] && child + 1 <
    usedSize):判断右孩子是否存在且更大,若满足则将 child 指向右孩子 —— 这样 child 就代表了父节点的最大子节点下标。
  • 后续会基于 child
    下标,比较父节点与最大子节点的大小,若父节点更小则交换二者位置,并继续向下调整(这段代码未完整写出交换逻辑,但结构是向下调整的核心框架)。
    把一个普通数组通过 “向下调整” 的方式,逐步构建成大根堆
    i
  • f(elem[child] > elem[parent]):判断当前子节点(已确定是父节点的最大子节点)的值是否大于父节点。如果成立,说明这对父子不满足大根堆的 “父≥子” 规则,需要调整。
  • int temp = elem[parent]; elem[parent] = elem[child]; elem[child] =
    temp;:交换父节点和子节点的元素,让大的元素上浮到父节点位置。
  • parent = child; child = 2*parent+1;:更新父节点为当前子节点的下标,然后计算新的左孩子下标
    ——继续向下调整下一层子树,确保调整后整个子树都满足大根堆的性质。
public void createHeap() {
//(usedSize - 1 - 1) / 2 是计算堆中最后一个非叶子节点的下标
for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {
siftDown(parent,usedSize);
}
}
/**
* 向下调整的方法
* @param parent 每棵子树的根节点下标
* @param usedSize 每棵子树是否调整结束的位置
*/
private void siftDown(int parent, int usedSize) {
int child = 2 * parent + 1;
//说明 至少有一个左孩子
//至少有一个左孩子
//elem[child + 1]这里可能会产生child + 1 的空
//             if(elem[child] < elem[child + 1]){
//                 child++;
//             }
while (child < usedSize) {
if(child+1 < usedSize && elem[child] < elem[child+1]) {
child++;
}
//代码走到这里 表示 child下标 一定是最大孩子的下标
if(elem[child] > elem[parent]) {
//交换
swap(child,parent);
parent = child;
child = 2*parent+1;
}else {
break;
}
}
}

完全二叉树的特性是:最后一个非叶子节点的子节点,是堆中 “最后一个元素”(叶子节点)。
数组下标的父子关系
在堆的数组存储中,若某个节点的下标为 child(叶子节点),则其父节点的下标为 (child - 1) / 2(整数除法)。
例如:若最后一个元素的下标是 4(即 usedSize - 1 = 4),则它的父节点下标为 (4 - 1) / 2 = 1(整数除法,3/2=1)。

(usedSize - 1 - 1) / 2 是计算堆中最后一个非叶子节点的下标

堆的底层是完全二叉树,存储在数组中,遵循以下下标关系:

  • 若某个节点的下标为 i,则其左子节点下标为 2*i + 1右子节点下标为 2*i + 2
  • 反过来,若某个子节点的下标为 child,则其父节点下标为 (child - 1) / 2(整数除法)。
  • usedSize - 1 是堆中最后一个元素的下标(因为 usedSize 是有效元素个数,数组下标从 0 开始)。
  • (usedSize - 1 - 1) / 2 等价于 (最后一个元素的下标 - 1) / 2,即通过最后一个元素(叶子节点)的下标,计算出它的父节点下标 —— 这个父节点就是堆中最后一个非叶子节点

堆的时间复杂度:

在这里插入图片描述
建堆的时间复杂度为O(N)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/964754.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Codeforces Round 1063 (Div. 2)题解

A. Souvlaki VS. Kalamaki 【题目】 给定一个长为n的数组nums,A,B两个人轮流行动。A先开始 第i轮,当前行动人可以跳过或者交换nums[i]和nums[i+1] 开始时A可以对nums任意排序。 要求最后nums必须是非递减,则A胜否则…

system自启动

system自启动[Unit] Description=Docker Application Container Engine Documentation=https://docs.docker.com BindsTo=containerd.service After=network-online.target containerd.service Wants=network-online.t…

25.11.13联考题解

A 神人构造,随机区分度真恶心。 我们考虑将序列分成前半段限制为 \(m\) 和后半段限制为 \(m=0\)。前面我们用 \(n,n-1,\dots,n-m+1\) 并让其合法即可,考虑后面的构造。考虑把序列分成尽量相等的三段,然后大的两段从…

2025.11.13模拟赛

赛场心态下去了,回不来了,悲( T3有望做出来的,没想dp 考虑到其实可以考虑只有一个区间变为大区间然后统计答案即可,考场上想了半天这东西怎么维护 其实拿一个线段树用脚区间+1,-1,维护区间标记为0的权值和 这个…

[CSP-S 2025] 道路修复 road

T2 道路修复(road) 如果不加乡镇,也就是第一档部分分,这就是一个裸的最小生成树模板,kruskal 直接做。 发现乡镇的范围很小只有 \(5-10\),考虑 \(2^k\) 枚举哪些乡镇要用,直接把启用乡镇的代价加到边权和里然后把…

[CSP-S 2025] 社团招新 club

T1社团招新(club) 原题链接 T1出这个... 以下规定三个社团分别为 \(a,b,c\)。 第一眼的思路尝试对每个人对三个社团的满意度取 \(max\),然后依次选最优的,很快发现这么做不行,因为有可能在满足限制后其他人能带来的…

【排查实录】Web 页面能打开,服务器能通接口,客户端却访问失败?原因全在这! - 实践

【排查实录】Web 页面能打开,服务器能通接口,客户端却访问失败?原因全在这! - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important;…

s2 NOIP模拟赛15-div2新太阳睡觉中心

新太阳睡觉中心 题面 原题链接 题解 简单计数题,但再给出一种与场上做法不一样的做法。 考虑总和转期望。将答案除以 \(2^k\),则为将 \(-1\) 随机确定为 \(01\) 时答案的期望。 根据题目描述,我们对于每一段连续的 …

LCA-雷达题解

雷达 题面 在 \(n \times n\) 的方格上,每个方格都有权值 \(a_{i,j}\) ,可花费 \(a_{i,j}\) 的代价覆盖以 \((i,j)\) 为中心,大小为 \(n \times n\) 的正方形区域。求最小的代价使得整片方格被覆盖。 题解 除了中心…

[USACO24JAN] Cowlendar S题解

[USACO24JAN] Cowlendar S 题面 原题链接 简介:给出 \(a_1....a_n\),对所有满足 s 的 \(L\) 求和 s 为:\(\forall i,4 \times L \leq a_i\) \(a_i \bmod L\) 不超过 \(3\) 种不同的值。\(1 \leq a_i \leq 4 \cdot 1…

2025年11月粮库空调,恒温粮库空调,一体式粮库空调厂家最新推荐,储粮控温权威测评与采购指南!

粮库空调作为保障粮食存储安全的关键设备,其应用场景覆盖了粮食存储的多个核心领域,不同场景对设备的温湿度控制精度、稳定性等需求各有侧重。而广州沃克斯顿环境设备有限公司凭借丰富的产品类型与过硬的技术实力,在…

CF 2093G Shorten the Array

T2 CF 2093G Shorten the Array 原题链接 本着不轻易上算法的原则想了半天,最后还是 01 trie 做完了。 如果只要求异或和为 \(k\) ,就可以用 map 维护每个数出现的最晚的位置,根据异或的性质直接查找需要的数字,统…

【A】Shinichi Kudo

https://www.luogu.com.cn/training/873086 qoj14429. Sequence Is Not Subsequence 下记 \(f(S)\) 表示 \(S\) 的答案。\(f(aaa...a)=|S-1|\times a\)。 \(f(ab)=ba\)。 \(f(abS)=ba+f(bS)\)。P14134 【MX-X22-T5】「…

如何在团队士气低落时重建信任与动力

团队士气低落是组织面临的严峻挑战,其根源往往在于信任缺失、方向迷茫或持续的压力。要在这种情况下重建信任与动力,管理者必须采取一套系统性且以人为本的策略。核心在于立即开启透明、诚实的双向沟通,主动承认问题…

noip2023T3 题解

Ad-hoc 题 这里仅考虑 \(f>g\) 考虑暴力 dp \(dp_{i,j}\) 表示第一个序列遍历到 \(i\) 项,第二个序列遍历到 \(j\) 项。 容易得到转移式子 \(dp_{i,j} = [a_i>b_j]\times [dp_{i-1,j}|dp_{i-1,j-1}|dp_{i,j-1}]…

#题解#牛客: 小心火烛的歪#枚举组合#位运算#dfs#

传送门 分析 1.这是一个枚举组合求最优的问题:集合大小q为7,可以用位运算来进行组合枚举 2.若点火方案f[i][j]==g[i][j]=1则,该方案一定不能用 3.令 g[i][j]+=f[i][j],若该方案可用且在枚举子集内,最终g中没有0则…

20251113周四日记

20251113周四日记今日: 1.早上回学校,开始看3b1b的深度学习课。写笔记。 2.中午和陈全去吃了萨莉亚,回来继续看课写笔记。 3.晚上和同门去吃饭,回来继续看Chapter7以及对diffusion模型的讲解。没事干了。 3Blue1Br…

2025.11.12 周作业 43(并非)速通

闲话 卡了好几道题了,怎么回事呢。 A. CF1796C 不难想到最优解应该是某个数 \(x\) 不断乘上 \(t\),即这个集合(大致)可以表示为: \[\{x, x \times t, x \times t^2, \dots, x \times t^k\} \]容易想到令 \(x=2\) …

2025 年 11 月螺丝打包机,五金打包机,称重打包机厂家最新推荐,权威测评排名与工业采购选择指南!

2在工业 4.0 浪潮下,螺丝、五金等零部件包装的效率与精度,直接影响企业生产流转速度与成本控制。传统包装模式面临人工依赖强、混料风险高、效率低下等痛点,而优质的打包设备成为制造业降本增效的关键抓手。温州工友…