当前位置: 首页 > news >正文

优先级队列的学习

优先级队列

队列是一种先进先出(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.dtcms.com/a/488030.html

相关文章:

  • 如今做那些网站致富苏州手机社区网站建设
  • 广州青菜篮农业有限公司网站建设项目东莞公司做网站
  • 如何登录网站服务器昆明营销网站建设
  • 东莞市企业网站建设哪家好什么网站可以做推广的
  • 3DMAX神经网络拓扑结构图插件NeuralNetworkTopology使用方法详解
  • 运 算 符
  • 项目商业网站建设方案长春市网站优化公司
  • Oracle下载JDK无需登录
  • 模拟算法专题总结:直接按题意实现的艺术
  • 昭阳区住房和城乡建设管理局网站网站建设最低价
  • 济南市住房和城乡建设局网站wordpress多图主题
  • TOP TOY闯关港股上市:三大关键挑战亟待破局,品牌如何独立增长?
  • TDengine 数学函数 FLOOR 用户手册
  • 第三方课题验收测试机构:【API测试工具Apifox使用指南】
  • 前端-APIs-day2
  • 织梦个人网站模板西安旅游
  • 个人网站设计与实现源码在线做网站黄
  • Highcharts 绘制之道(1):用数据构建基础图形
  • 【机器学习02】梯度下降、多维特征线性回归、特征缩放
  • 一个网站每年维护费用品牌营销网站
  • 有哪些做的很漂亮的网站商城小程序介绍
  • Vue3+Three.js:第05期 时间控制,requestAnimationFrame vs Clock
  • 松江做微网站电子商务网站的优点有那些
  • 个体营业执照网站备案做网站都需要用到什么
  • Python CGI 编程
  • 网页传奇平台优化关键词的作用
  • 定制网站建设和运营网站开发合同履约
  • java枚举能继承接口吗
  • 三分钟学懂3D建模中的UV Position Map
  • 广州网站推广公司wordpress 教学