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-数据结构-优先级队列(堆)
一、优先级队列 ① 什么是优先级队列? 在此之前,我们已经学习过了"队列"的相关知识,我们知道"队列"是一种"先进先出"的数据结构,我们还学习过"栈",是"后进先出"的…...

C++实现状态模式
首先上代码: #include <iostream> #include <memory>class Context;class State { public:virtual void Handle(Context * context) 0; //纯虚函数virtual ~State() default; //虚析构函数 };//创建状态A class ConcreateStateA : public State{…...

FreeRTOS学习笔记2:FreeRTOS的基础知识
1.FreeRTOS介绍 FreeRTOS是一个免费的嵌入式实时操作系统,同时它在市面上也是一款主流的操作系统,是工作上必不可少的技能。它具有以下六种特点: 1.免费开源:在商业产品中使用,无潜在商业风险,无需担心。 2…...

计算机网络之计算机网络的分类
计算机网络可以根据不同的角度进行分类,以下是几种常见的分类方式: 1. 按照规模和范围: 局域网(LAN,Local Area Network):覆盖较小范围(例如一个建筑物或校园)…...

从理论到实践:Linux 进程替换与 exec 系列函数
个人主页:chian-ocean 文章专栏-Linux 前言: 在Linux中,进程替换(Process Substitution)是一个非常强大的特性,它允许将一个进程的输出直接当作一个文件来处理。这种技术通常用于Shell脚本和命令行操作中…...

Flutter常用Widget小部件
小部件Widget是一个类,按照继承方式,分为无状态的StatelessWidget和有状态的StatefulWidget。 这里先创建一个简单的无状态的Text小部件。 Text文本Widget 文件:lib/app/app.dart。 import package:flutter/material.dart;class App exte…...

微信小程序实战0 设置
1.调节模拟器到右侧位置 2.设置编辑页面的字体和行距。...

2025开源DouyinLiveRecorder全平台直播间录制工具整合包,多直播同时录制、教学直播录制、教学视频推送、简单易用不占内存
一、DouyinLiveRecorder软件介绍(文末提供下载) 官方地址:GitHub - ihmily/DouyinLiveRecorder 本文信息来源于作者GitHub地址 一款简易的可循环值守的直播录制工具,基于FFmpeg实现多平台直播源录制,支持自定义配置录制…...

使用 postman 测试思源笔记接口
思源笔记 API 权鉴 官方文档-中文:https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md 权鉴相关介绍截图: 对应的xxx,在软件中查看 如上图:在每次发送 API 请求时,需要在 Header 中添加 以下键值对&a…...

当WebGIS遇到智慧文旅-以长沙市不绕路旅游攻略为例
目录 前言 一、旅游数据组织 1、旅游景点信息 2、路线时间推荐 二、WebGIS可视化实现 1、态势标绘实现 2、相关位置展示 三、成果展示 1、第一天旅游路线 2、第二天旅游路线 3、第三天旅游路线 4、交通、订票、住宿指南 四、总结 前言 随着信息技术的飞速发展&…...

阿里最新普通x231 逆向分析
声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 逆向前言 12月份的时候更新了过一次…...

php的使用及storm环境部署
php语法 环境搭建:在小皮中新建网站,注意先填写域名再点击选择根目录。 成功创建网站后,打开发现forbidden,因为新建的网站里是空的,需要新建index.php文件----> 在Phpstorm中左上角打开文件,打开那个文…...

高可用 Keepalived 服务部署流程
一、配置文件 vim /etc/keepalived/keepalived.confGLOBAL CONFIGURATION --- 全局配置部分VRRPD CONFIGURATION --- VRRP协议配置部分LVS CONFIGURATION --- LVS服务管理配置部分[rootlb01 ~]# cat /etc/keepalived/keepalived.…...

【新春特辑】2025年1月科技浪潮中的AI最新时事与科技趋势
2025年1月科技浪潮中的AI最新时事与科技趋势 一、AI科技时事 人工智能代理(AI Agent)的发展 最新进展:人工智能代理正逐步成为科技领域的新热点。这些代理能够自主执行特定任务,如管理日程、回复邮件等。然而,它们仍…...

解决Django非ORM模型提示初始化request问题
提问 Django在DRF时候自定义显示一些非model的字段提示TypeError: Field.__init__() got an unexpected keyword argument request 解答1 错误提示 TypeError: Field.__init__() got an unexpected keyword argument request 显示在创建序列化器实例时,传递了一个…...

G. XOUR
题目链接:Problem - G - Codeforces 题目大意:给你一个n长的序列, 其中你可以将a[i] XOR a[j] 的值 严格小于4的数对进行交换。 你可以操作任何几次, 让最后的数列最小。如果在 x 和 y 不同的第一个位置, xi<yi &…...

有没有个性化的UML图例
绿萝小绿萝 (53****338) 2012-05-10 11:55:45 各位大虾,有没有个性化的UML图例 绿萝小绿萝 (53****338) 2012-05-10 11:56:03 例如部署图或时序图的图例 潘加宇 (35***47) 2012-05-10 12:24:31 "个性化"指的是? 你的意思使用你自己的图标&…...

【RAG】SKLearnVectorStore 避免使用gpt4all会connection err
gpt4all 列表中包含了多个开源的大模型,如 Qwen2.5、Llama 3、DeepSeek、Mistral 等,但 不包含 OpenAI 的 GPT-4o。GPT-4o 是 OpenAI 提供的闭源模型,目前只能通过 OpenAI API 或 ChatGPT 官方应用(网页版、移动端)访问,并不支持本地运行,也没有 GGUF 量化格式的模型文件…...

vue框架技术相关概述以及前端框架整合
vue框架技术概述及前端框架整合 1 node.js 介绍:什么是node.js Node.js就是运行在服务端的JavaScript。 Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎。 作用 1 运行java需要安装JDK,而Node.js是JavaScript的运行环…...

Spring Boot + Facade Pattern : 通过统一接口简化多模块业务
文章目录 Pre概述在编程中,外观模式是如何工作的?外观设计模式 UML 类图外观类和子系统的关系优点案例外观模式在复杂业务中的应用实战运用1. 项目搭建与基础配置2. 构建子系统组件航班服务酒店服务旅游套餐服务 3. 创建外观类4. 在 Controller 中使用外…...

牛客周赛 Round 78
题目目录 A-时间表查询!解题思路参考代码 B-一起做很甜的梦!解题思路参考代码 C-翻之解题思路参考代码 D-乘之解题思路参考代码 E-在树上游玩解题思路参考代码 A-时间表查询! \hspace{15pt} 今天是2025年1月25日,今年的六场牛客寒…...
【机器学习】自定义数据集 ,使用朴素贝叶斯对其进行分类
一、贝叶斯原理 贝叶斯算法是基于贝叶斯公式的,其公式为: 其中叫做先验概率,叫做条件概率,叫做观察概率,叫做后验概率,也是我们求解的结果,通过比较后验概率的大小,将后验概率最大的…...

02.01 生产者消费者
请使用条件变量实现2生产者2消费者模型,注意1个生产者在生产的时候,另外一个生产者不能生产。 1>程序代码 #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h>…...

mac 手工安装OpenSSL 3.4.0
如果你希望继续安装 openssl-3.4.0 而不是降级到 3.1.1,可以尝试以下解决方案。根据你提供的错误信息,问题可能出在测试阶段(make test),我们可以尝试跳过测试或修复测试失败的原因。 --- ### **解决方案:…...

kamailio-ACC_JSON模块详解【后端语言go】
要确认 ACC_JSON 模块是否已经成功将计费信息推送到消息队列(MQueue),以及如何从队列中取值,可以按照以下步骤进行操作: 1. 确认 ACC_JSON 已推送到队列 1.1 配置 ACC_JSON 确保 ACC_JSON 模块已正确配置并启用。以下…...

ArkTS语言介绍
文章目录 一、基本知识声明类型运算符语句函数函数声明可选参数Rest参数返回类型函数的作用域函数调用函数类型箭头函数(又名Lambda函数)闭包函数重载类字段方法构造函数可见性修饰符对象字面量抽象类接口接口属性接口继承抽象类和接口泛型类型和函数泛型类和接口泛型约束泛型…...

海外问卷调查之渠道查,企业经营的指南针
海外问卷调查,是企业调研最常用到的方法,有目的、有计划、有系统地收集研究对象的现实状况或历史状况的一种有效手段,是指导企业经营的有效手段。 海外问卷调查充分运用历史法、观察法等方法,同时使用谈话、问卷、个案研究、测试…...

spring和Mybatis的逆向工程
在现代企业级开发中,使用Spring和MyBatis进行快速、高效的数据库操作是非常常见的。本文将深入探讨如何使用Spring和MyBatis进行逆向工程,帮助开发者自动生成数据库相关的代码,提高开发效率和代码质量。 一、什么是逆向工程 逆向工程是指从…...

【Android】问deepseek存储访问
这些天deepseek爆火,我们来问问android问题看看,如果问android中的应用怎么访问外部存储,回答的很清楚,但是如果问的深入一些,比如Android中是怎么控制让应用不能读取其他应用的外部存储文件的,回答的比较抽…...

Android记事本App设计开发项目实战教程2025最新版Android Studio
平时上课录了个视频,从新建工程到打包Apk,从头做到尾,没有遗漏任何实现细节,欢迎学过Android基础的同学参加,如果你做过其他终端软件开发,也可以学习,快速上手Android基础开发。 Android记事本课…...