Java-数据结构-优先级队列(堆)
一、优先级队列
① 什么是优先级队列?
在此之前,我们已经学习过了"队列"的相关知识,我们知道"队列"是一种"先进先出"的数据结构,我们还学习过"栈",是"后进先出"的数据结构,这两种数据结构通常能够帮助我们在解题时存储一些数据,但是经过一段时间的练习,我们应该也能意识到这两种数据结构的缺点:"不够灵活"。
因为在有些情况下,我们要存储的数据并非是只存取队头或者队尾,有时我们还想使数据拥有一种排序的规律,使得我们能够随时取出和存入"按照这种规律下,优先级较高的数据",而想要实现这一点,即便是"栈"与"队列"两者的结合"双端队列",都是无法轻易办到的。
📚 比如:有时考试我们会按照学生的成绩进行分配考场,想在一个考场中提出或者放入一个学生,就要根据他的考试成绩来判断。

于是——优先级队列(PriorityQueue)出现了
二、优先级队列的模拟实现
优先级队列是一种能够按照指定的排序方式,自动将数据存到合理的位置的数据结构。它的底层实际上就是通过类似二叉树的结构来实现的~
优先级队列可以分为两种:
📕 大根堆(这棵二叉树中,每棵树的根节点都比它左右子树的根节点大)
📕 小根堆(这棵二叉树中,每棵树的根节点都比它左右子树的根节点小)
(需要注意的是:根总是一棵完全二叉树)
📚 比如:此时我们有一组数据为{10,7,12,6,8,4},那么按照小根堆的形式存储它,就是:

这样的一颗二叉树,那么接下来我们一点点的去探究,优先级队列究竟是如何实现的:
① 基本框架
(java默认的优先级队列为小根堆,所以我们这里尝试模拟实现一个大根堆)
与之前一些数据结构的模拟实现较为类似,由于"堆"是一棵完全二叉树,所以这里我们的模拟实现就不再使用链表了。而是直接使用数组进行实现(通过层序遍历的顺序),所以我们需要最基本的一个整数数组elem,还需要一个记录有效数据个数的usedSize~
import java.util.*;public class MyHeap {public int[] elem;public int usedSize;public MyHeap() {}//对elem进行初始化public void init(int[] array) {}//创建一个优先级队列public void createHeap(int[] array) {}//向下调整private void shiftDown(int root,int len) {}//元素入队(不破坏优先级队列结构)public void push(int val) {}//向上调整private void shiftUp(int child) {}//判满public boolean isFull() {}//出队public void pollHeap() {}//判空public boolean isEmpty() {}//获取队顶元素public int peekHeap() {}
}
② 初始化elem
📚 思路提示:
该方法用于我们先将数组存入elem中,以便我们后续的操作,我们只需要将数组中的元素存入elem,并且在初始化elem途中,适时的对elem进行扩容即可~
📖 代码示例:
//对elem进行初始化public void init(int[] array) {int len = array.length;for (int i = 0; i < len; i++) {if (isFull()) {elem = Arrays.copyOf(elem, elem.length * 2);}elem[i] = array[i];usedSize++;}}
//判满public boolean isFull() {return usedSize == elem.length;}
③ 堆的向下调整
📚 思路提示:
想要创建一个大根堆,就需要我们上面提到的大根堆性质"每棵树的根节点都比它左右子树的根节点大"
而想要使我们的elem中的元素也遵从这种规律,我们就要知道应该如何去对堆中的元素位置进行调整。其实还是比较简单的:
📚 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有结点从0开始编号,则对于序号为i的结点有以下性质:
📕 若孩子节点下标为 i ,那么父亲结点为 (i - 1) / 2 [i > 0]
📕 若父亲节点下标为 i ,那么左孩子节点为 2 * i + 1 [2i + 1 < n],右孩子节点为 2 * i + 2 [2i + 2 < n]
📕 首先,将需要调整的元素下标记作parent
📕 如果parent存在左孩子,则通过公式,算出parent的左孩子结点child
📕 如果有两个孩子结点,则将孩子结点标记为值最大的孩子
📕 判断孩子结点与父亲结点的大小,如果孩子更大,则交换孩子与父亲结点(大根堆)
📕 如果交换了结点,则将parent标记为child,再重新求child
⭐ 图示:

📖 代码示例:
//向下调整private void shiftDown(int parent,int len) {int child = parent * 2 + 1;while(child < len){//判断是否有右孩子,并且右孩子是否更大if(child + 1 < len && elem[child + 1] > elem[child]){child++;}if(elem[child] > elem[parent]){swap(elem,child,parent);parent = child;child = parent * 2 + 1;}else {break;}}}
public void swap(int[] arr,int i,int j){int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}

④ 堆的创建
📚 思路提示:
我们刚刚所实现的"向下调整",其实就是为了这一步我们成功的构建出一个"大根堆"而进行的铺垫
我们可以注意到,在上面的"向上调整"中,我们每一次进行交换,其实就是创建出了一个"这三个元素组成的局部大根堆",而想将整个数组都变成大根堆,我们就可以对数组中每一个有子节点的根进行向下调整~
📖 代码示例:
//创建一个优先级队列public void createHeap() {int len = elem.length;int parent = (elem.length - 1 - 1) / 2;for(int i = parent;i >= 0;i--){shiftDown(i,len);}}
这里或许大家会有一个疑问:为什么从最后一个根节点向上调整,而不是从第一个根节点向下调整?这是因为,两种看似一样的方法,其实时间复杂度相差并不小:
📕 如果从第一个根节点开始向下调整:
对于根节点(位于第 1 层),它可能需要向下调整到叶子节点(第 log n 层),因此调整的时间复杂度是 O(log n)。
对于第 2 层的节点,它们可能需要向下调整到第 log n - 1 层,时间复杂度是 O(log n - 1)。
以此类推,直到叶子节点(不需要调整)。
这种方式的调整,时间复杂度为O(nlogn)
📕 如果从最后一个根节点向上调整:
直接能够越过所有的叶子节点(最好的情况占结点总数一半 + 1)
对于剩余的结点,调整取决于它们的高度,并且越接近根节点,需要调整的高度越低。
这种方式的调整,时间复杂度接近O(n)

⑤ 堆的插入
📚 思路提示:
顾名思义,就是在堆中插入一个元素,但是我们需要注意的是,不论是此刻的插入,亦或是之后我们需要进行的删除,都要保证操作后仍然为大根堆。所以在我们将新的元素插入到堆的末尾后,还要将该元素不断地向上进行调整,直到在某一刻符合大根堆的条件,停止调整。
📕 对堆进行判满,如果堆已满则进行扩容
📕 将新的元素插入堆的末尾,usedSize++
📕 对新元素进行向上调整:
📕 将元素下标标记为child,并且通过公式求出它的parent
📕 如果 parent >= 0 判断child和parent的值,如果child更大,两者进行交换,否则调整结束
(因为在此之前,已经是标准的大根堆,所以不需要进行两个child的判断,只调整目标结点即可)
📕 如果进行了交换,则再将parent = child,然后重新求parent
⭐ 图示:

📖 代码示例:
//元素入队(不破坏优先级队列结构)public void push(int val) {if(isFull()){elem = Arrays.copyOf(elem, elem.length * 2);}elem[usedSize] = val;shiftUp(usedSize++);}
//向上调整private void shiftUp(int child) {int parent = (child - 1) / 2;while(parent >= 0){if(elem[child] > elem[parent]){swap(elem,child,parent);child = parent;parent = (child - 1) / 2;}else {break;}}}

⑥ 堆的删除
(注意:堆的删除指的是删除堆顶元素)
📚 思路提示:
想要删除一个堆顶元素,我们首先知道的是"usedSize"需要减一,那么我们由这点思考一下,如果不进行任何操作,首先usedSize--后,我们的堆中实际上消失的是哪个元素?没错,就是堆中的最后一个元素~,但是我们想删除的并不是该元素,而是我们的堆顶元素,简单呀~将这两个元素互换一下不就好了~最后再将新的堆顶元素进行向下调整即可~
📕 首先,判断该堆是否为空,若为空则直接退出该方法
📕 将堆顶元素与堆中最后一个元素进行交换
📕 然后将usedSize--
📕 最后将新的堆顶元素进行向下调整
⭐ 图示:

📖 代码示例:
//出队public void pollHeap() {if(isEmpty()){return;}swap(elem,0,--usedSize);shiftDown(0,usedSize);}
//判空public boolean isEmpty() {return usedSize == 0;}
//获取队顶元素public int peekHeap() {return elem[0];}

完整代码:
import java.util.*;public class MyHeap {public int[] elem;public int usedSize;public MyHeap(){elem = new int[10];}//创建一个大根堆public void creative(int[] arr){for(int i = 0;i < arr.length;i++){if(isFull()){elem = Arrays.copyOf(elem,elem.length * 2);}elem[i] = arr[i];usedSize++;}}public void doArr(){//最后一个叶子结点的父结点int start = (usedSize - 1 - 1) / 2;for(int i = start;i >= 0;i--){siftDown(i,usedSize);}}//将较大的子节点与父结点交换//向下调整public void siftDown(int parent,int end){//找到父结点的的左子结点int child = parent * 2 + 1;//保证该子结点未越界while(child < end){//查找左右子结点中的最大子结点//(包含了找出最大值后,对右子节点的判断)if(child + 1 < end && elem[child] < elem[child + 1]){child++;}//判断是否需要与父结点交换(判断大小)if(elem[child] > elem[parent]){int tmp = elem[child];elem[child] = elem[parent];elem[parent] = tmp;parent = child;child = parent * 2 + 1;}else {break;}}}//向堆中添加新元素public void offer(int val){if(isFull()){elem = Arrays.copyOf(elem,elem.length * 2);}elem[usedSize] = val;siftUp(usedSize);usedSize++;}//向上调整public void siftUp(int child){int parent = (child - 1) / 2;while(parent >= 0){if(elem[child] > elem[parent]){int tmp = elem[child];elem[child] = elem[parent];elem[parent] = tmp;child = parent;parent = (child - 1) / 2;}else {break;}}}//删除元素public int poll(){int oldValue = elem[0];elem[0] = elem[--usedSize];siftDown(0,usedSize);return oldValue;}//将堆排序成从小到大的顺序public void heapSort(){int end = usedSize - 1;while(end > 0){int tmp = elem[end];elem[end] = elem[0];elem[0] = tmp;siftDown(0,end);end--;}}public boolean isFull(){return usedSize >= elem.length;}
}
这篇文章我们对"优先级队列"进行了基本的认识,并且自己也尝试着模拟实现了一个大根堆,关于"优先级队列"的后续知识我将在后面的文章中为大家讲解,那么这篇文章到这里就结束啦,作者能力有限,如果有哪里说的不够清楚或者不够准确,还请各位在评论区多多指出,我也会虚心学习的,我们下次再见啦
相关文章:
Java-数据结构-优先级队列(堆)
一、优先级队列 ① 什么是优先级队列? 在此之前,我们已经学习过了"队列"的相关知识,我们知道"队列"是一种"先进先出"的数据结构,我们还学习过"栈",是"后进先出"的…...
爬虫基础(四)线程 和 进程 及相关知识点
目录 一、线程和进程 (1)进程 (2)线程 (3)区别 二、串行、并发、并行 (1)串行 (2)并行 (3)并发 三、爬虫中的线程和进程 &am…...
C语言初阶力扣刷题——349. 两个数组的交集【难度:简单】
1. 题目描述 力扣在线OJ题目 给定两个数组,编写一个函数来计算它们的交集。 示例: 输入:nums1 [1,2,2,1], nums2 [2,2] 输出:[2] 输入:nums1 [4,9,5], nums2 [9,4,9,8,4] 输出:[9,4] 2. 思路 直接暴力…...
Tailwind CSS - Tailwind CSS 引入(安装、初始化、配置、引入、构建、使用 Tailwind CSS)
一、Tailwind CSS 概述 Tailwind CSS 是一个功能优先的 CSS 框架,它提供了大量的实用类(utility classes),允许开发者通过组合这些类来快速构建用户界面 Tailwind CSS 与传统的 CSS 框架不同(例如,Bootstr…...
Sqoop导入MySQL中含有回车换行符的数据
个人博客地址:Sqoop导入MySQL中含有回车换行符的数据 MySQL中的数据如下图: 检查HDFS上的目标文件内容可以看出,回车换行符位置的数据被截断了,导致数据列错位。 Sqoop提供了配置参数,在导入时丢弃掉数据的分隔符&…...
LightM-UNet(2024 CVPR)
论文标题LightM-UNet: Mamba Assists in Lightweight UNet for Medical Image Segmentation论文作者Weibin Liao, Yinghao Zhu, Xinyuan Wang, Chengwei Pan, Yasha Wang and Liantao Ma发表日期2024年01月01日GB引用> Weibin Liao, Yinghao Zhu, Xinyuan Wang, et al. Ligh…...
stm32硬件实现与w25qxx通信
使用的型号为stm32f103c8t6与w25q64。 STM32CubeMX配置与引脚衔接 根据stm32f103c8t6引脚手册,采用B12-B15四个引脚与W25Q64连接,实现SPI通信。 W25Q64SCK(CLK)PB13MOSI(DI)PB15MISO(DO)PB14CS(…...
FPGA 使用 CLOCK_DEDICATED_ROUTE 约束
使用 CLOCK_DEDICATED_ROUTE 约束 CLOCK_DEDICATED_ROUTE 约束通常在从一个时钟区域中的时钟缓存驱动到另一个时钟区域中的 MMCM 或 PLL 时使 用。默认情况下, CLOCK_DEDICATED_ROUTE 约束设置为 TRUE ,并且缓存 /MMCM 或 PLL 对必须布局在相同…...
一个开源 GenBI AI 本地代理(确保本地数据安全),使数据驱动型团队能够与其数据进行互动,生成文本到 SQL、图表、电子表格、报告和 BI
一、GenBI AI 代理介绍(文末提供下载) github地址:https://github.com/Canner/WrenAI 本文信息图片均来源于github作者主页 在 Wren AI,我们的使命是通过生成式商业智能 (GenBI) 使组织能够无缝访问数据&…...
C动态库的生成与在Python和QT中的调用方法
目录 一、动态库生成 1)C语言生成动态库 2)c类生成动态库 二、动态库调用 1)Python调用DLL 2)QT调用DLL 三、存在的一些问题 1)python调用封装了类的DLL可能调用不成功 2)DLL格式不匹配的问题 四、…...
C++ Primer 自定义数据结构
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
解析 Oracle 中的 ALL_SYNONYMS 和 ALL_VIEWS 视图:查找同义词与视图的基础操作
目录 前言1. ALL_SYNONYMS 视图2. ALL_VIEWS 视图3. 扩展 前言 🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF 1. ALL_SYNONYMS 视图 在 Oracle 数据库中,同义词(Synonym)是对数…...
PyTorch框架——基于深度学习YOLOv8神经网络学生课堂行为检测识别系统
基于YOLOv8深度学习的学生课堂行为检测识别系统,其能识别三种学生课堂行为:names: [举手, 读书, 写字] 具体图片见如下: 第一步:YOLOv8介绍 YOLOv8 是 ultralytics 公司在 2023 年 1月 10 号开源的 YOLOv5 的下一个重大更新版本…...
深入探索 Vue 3 Markdown 编辑器:高级功能与实现
目录 1. 为什么选择 Markdown 编辑器?2. 选择合适的 Markdown 编辑器3. 安装与基本配置安装 配置 Markdown 编辑器代码说明 4. 高级功能实现4.1 实时预览与双向绑定4.2 插入图片和图像上传安装图像上传插件配置图像上传插件 4.3 数学公式支持安装 KaTeX配置 KaTeX 插…...
7.攻防世界fileclude
题目描述 进入题目页面如下 看到题目提示应该为文件包含漏洞 解释上述代码 // 输出提示信息:错误的方式! WRONG WAY! <?php // 包含名为 "flag.php" 的文件,通常这个文件里可能包含重要的敏感信息,如 flag inclu…...
【自然语言处理(NLP)】深度学习架构:Transformer 原理及代码实现
文章目录 介绍Transformer核心组件架构图编码器(Encoder)解码器(Decoder) 优点应用代码实现导包基于位置的前馈网络残差连接后进行层规范化编码器 Block编码器解码器 Block解码器训练预测 个人主页:道友老李 欢迎加入社…...
【HarmonyOS之旅】基于ArkTS开发(三) -> 兼容JS的类Web开发(二)
目录 1 -> HML语法 1.1 -> 页面结构 1.2 -> 数据绑定 1.3 -> 普通事件绑定 1.4 -> 冒泡事件绑定5 1.5 -> 捕获事件绑定5 1.6 -> 列表渲染 1.7 -> 条件渲染 1.8 -> 逻辑控制块 1.9 -> 模板引用 2 -> CSS语法 2.1 -> 尺寸单位 …...
当WebGIS遇到智慧文旅-以长沙市不绕路旅游攻略为例
目录 前言 一、旅游数据组织 1、旅游景点信息 2、路线时间推荐 二、WebGIS可视化实现 1、态势标绘实现 2、相关位置展示 三、成果展示 1、第一天旅游路线 2、第二天旅游路线 3、第三天旅游路线 4、交通、订票、住宿指南 四、总结 前言 随着信息技术的飞速发展&…...
《大数据时代“快刀”:Flink实时数据处理框架优势全解析》
在数字化浪潮中,数据呈爆发式增长,实时数据处理的重要性愈发凸显。从金融交易的实时风险监控,到电商平台的用户行为分析,各行业都急需能快速处理海量数据的工具。Flink作为一款开源的分布式流处理框架,在这一领域崭露头…...
【机器学习理论】朴素贝叶斯网络
基础知识: 先验概率:对某个事件发生的概率的估计。可以是基于历史数据的估计,可以由专家知识得出等等。一般是单独事件概率。 后验概率:指某件事已经发生,计算事情发生是由某个因素引起的概率。一般是一个条件概率。 …...
使用Pygame制作“吃豆人”游戏
本篇博客展示如何使用 Python Pygame 编写一个简易版的“吃豆人(Pac-Man)” 风格游戏。这里我们暂且命名为 Py-Man。玩家需要控制主角在一个网格地图里移动、吃掉散布在各处的豆子,并躲避在地图中巡逻的幽灵。此示例可帮助你理解网格地图、角…...
使用 Docker(Podman) 部署 MongoDB 数据库及使用详解
在现代开发环境中,容器化技术(如 Docker 和 Podman)已成为部署和管理应用程序的标准方式。本文将详细介绍如何使用 Podman/Docker 部署 MongoDB 数据库,并确保其他应用程序容器能够通过 Docker 网络成功连接到 MongoDB。我们将逐步…...
SQL入门到精通 理论+实战 -- 在 MySQL 中学习SQL语言
目录 一、环境准备 1、MySQL 8.0 和 Navicat 下载安装 2、准备好的表和数据文件: 二、SQL语言简述 1、数据库基础概念 2、什么是SQL 3、SQL的分类 4、SQL通用语法 三、DDL(Data Definition Language):数据定义语言 1、操…...
Electricity Market Optimization 探索系列(一)
本文参考链接:Linear Programming Mini Example 先从一个线性规划的例子说起: 问题背景: 现在需要使用两台发电机满足用户的用电需求,发电机一的发电功率上限是 6MW,发电机二的发电功率上限是 4MW,发电…...
x86-64数据传输指令
关于汇编语言一些基础概念的更详细的介绍,可移步MIPS指令集(一)基本操作_mips指令 sw-CSDN博客 该指令集中一个字2字节。 该架构有16个64位寄存器,名字都以%r开头,每个寄存器的最低位字节,低1~2位字节&…...
【ESP32】ESP-IDF开发 | WiFi开发 | TCP传输控制协议 + TCP服务器和客户端例程
1. 简介 TCP(Transmission Control Protocol),全称传输控制协议。它的特点有以下几点:面向连接,每一个TCP连接只能是点对点的(一对一);提供可靠交付服务;提供全双工通信&…...
算法基础学习——快排与归并(附带java模版)
快速排序和归并排序是两种速度较快的排序方式,是最应该掌握的两种排序算法, (一)快速排序(不稳定的) 基本思想:分治 平均时间复杂度:O(nlogn) / 最慢O(n^2) / 最快O(n) 步骤&…...
指针的进化—sizeof和strlen对比(字符串和字符数组的区分)
1.前言 如果你对各个数组的内容存放是什么没有个清晰的概念,对指针偏移之后的数量算不出来或者模棱两可,那么本篇就来详细介绍sizeof和strlen来具象化的显示数组的内容存放了多少内容,偏移量变化后的变化,这个数组进行运算后会不会…...
TensorFlow简单的线性回归任务
如何使用 TensorFlow 和 Keras 创建、训练并进行预测 1. 数据准备与预处理 2. 构建模型 3. 编译模型 4. 训练模型 5. 评估模型 6. 模型应用与预测 7. 保存与加载模型 8.完整代码 1. 数据准备与预处理 我们将使用一个简单的线性回归问题,其中输入特征 x 和标…...
【memgpt】letta 课程1/2:从头实现一个自我编辑、记忆和多步骤推理的代理
llms-as-operating-systems-agent-memory llms-as-operating-systems-agent-memory内存 操作系统的内存管理...
