数据结构(Java)—— 优先级队列(堆)

news/2025/2/8 2:40:36/

1. 概念

    优先级队列是一种抽象数据类型(ADT),它允许队列中维护的元素按优先级排序,优先级最高的元素会优先被处理。

2. 使用

2.1 优先级队列的构造

构造器
功能介绍
PriorityQueue()
创建一个空的优先级队列,默认容量是11
PriorityQueue(int  initialCapacity)
创建一个初始容量为 initialCapacity 的优先级队列,注意:
initialCapacity不能小于 1 ,否则会抛 IllegalArgumentException 异常
PriorityQueue(Collection<?
extends E> c)
用一个集合来创建优先级队列

代码示例:

java">      public static void main(String[] args) {// 创建一个空的优先级队列,底层默认容量是11PriorityQueue<Integer> q1 = new PriorityQueue<>();// 创建一个空的优先级队列,底层的容量为initialCapacityPriorityQueue<Integer> q2 = new PriorityQueue<>(100);ArrayList<Integer> list = new ArrayList<>();list.add(4);list.add(3);list.add(2);list.add(1);// 用ArrayList对象来构造一个优先级队列的对象// q3中已经包含了三个元素PriorityQueue<Integer> q3 = new PriorityQueue<>(list);System.out.println(q3.size());System.out.println(q3.peek());}

运行结果如下:

注意:默认情况下, PriorityQueue 队列是小堆,如果需要大堆需要用户提供比较器

2.2 方法

函数名
功能介绍
boolean offer(E e)
插入元素 e ,插入成功返回 true ,如果 e 对象为空,抛出 NullPointerException 异常,注意:空间不够时候会进行扩容
E peek()
获取优先级最高的元素,如果优先级队列为空,返回null
E poll()
移除优先级最高的元素并返回,如果优先级队列为空,返回null
int size()
获取有效元素的个数
void clear()
清空
boolean isEmpty()
检测优先级队列是否为空,空返回true

代码示例

java">public static void main(String[] args) {int[] arr = {4,1,9,2,8,0,7,3,6,5};// 一般在创建优先级队列对象时,如果知道元素个数,建议就直接将底层容量给好// 否则在插入时需要不多的扩容// 扩容机制:开辟更大的空间,拷贝元素,这样效率会比较低PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);for (int e: arr) {q.offer(e);}System.out.println(q.size()); // 打印优先级队列中有效元素个数System.out.println(q.peek()); // 获取优先级最高的元素// 从优先级队列中删除两个元素之和,再次获取优先级最高的元素q.poll();q.poll();System.out.println(q.size()); // 打印优先级队列中有效元素个数System.out.println(q.peek()); // 获取优先级最高的元素q.offer(0);System.out.println(q.peek()); // 获取优先级最高的元素// 将优先级队列中的有效元素删除掉,检测其是否为空q.clear();if(q.isEmpty()){System.out.println("优先级队列已经为空!!!");}else{System.out.println("优先级队列不为空");}}

运行结果如下:

3. 模拟实现

    JDK1.8 中的 PriorityQueue 底层使用了堆这种数据结构 ,而堆实际就是在完全二叉树的基础上进行了一些调整。

3.1 堆

1. 概念

    如果有一个 关键码的集合 K = {k0 k1 k2 kn-1} ,把它的所有元素 按完全二叉树的顺序存储方式存储 在一 个一维数组中 ,并满足: Ki <= K2i+1 Ki<= K2i+2 (Ki >= K2i+1 Ki >=K2i+2) i = 0 1 2… ,则 称为小堆 ( 或大堆) 。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆

2. 性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。
3. 存储方式
从堆的概念可知, 堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储。

注意

   1. 对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低

   2. 将元素存储到数组中后,假设i为节点在数组中的下标,则有:

  • 如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
  • 如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子
  • 如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子

3.2 堆的创建(以小根堆为例)

数据以集合{ 27,15,19,18,28,34,65,49,25,37 }为例。

向下调整
1. parent 标记需要调整的节点, child 标记 parent 的左孩子 ( 注意: parent 如果有孩子一定先是有左孩子 )
2. 如果 parent 的左孩子存在,即 :child < size , 进行以下操作,直到 parent 的左孩子不存在:
       1. parent 右孩子是否存在,存在找到左右孩子中最小的孩子,让 child 进行标记
       2. 将 parent 与较小的孩子 child 比较,如果parent小于较小的孩子 child ,调整结束;
          否则:交换parent 与较小的孩子 child ,交换完成之后, parent 中大的元素向下移动,可能导致子树不满足对的性质,因此需要继续向下调整,即parent = child child = parent*2+1; 然后继续
代码示例:
java">    public int[] elem;public int usedSize;public TestHeap() {this.elem = new int[10];}public void init(int[] array) {for (int i = 0; i < array.length; i++) {elem[i] = array[i];usedSize++;}}//把elem数组当中的数据 调整为小根堆 public void createHeap() {for (int parent = (usedSize-1-1)/2; parent >= 0 ; parent--) {siftDown(parent,usedSize);}}private void swap(int i,int j) {int tmp = elem[i];elem[i] = elem[j];elem[j] = tmp;}public void siftDown(int parent,int end) {int child = 2*parent+1;while (child < end) {if(child+1 < end && elem[child] > elem[child+1]) {child++;}//child下标 就是 左右孩子的最小值if(elem[child] < elem[parent]) {swap(child,parent);parent = child;child = 2*parent+1;}else {break;}}}

运行结果如下:

注意:
   1. 在调整以 parent 为根的二叉树时,必须要满足 parent 的左子树和右子树已经是堆了才可以向下调整。
   2. 创建小根堆与大根堆的区别在于向下调整(siftDown),变化树中元素的比较关系就可以转换大小根堆。

3.3 插入

堆的插入总共需要两个步骤:
1. 先将元素放入到底层空间中 ( 注意:空间不够时需要扩容 )
2. 将最后新插入的节点向上调整,直到满足堆的性质
代码示例
java">public void offer(int val) {if(isFull()) {elem = Arrays.copyOf(elem,2*elem.length);}elem[usedSize] = val;usedSize++;//11siftUp(usedSize-1);}public void siftUp(int child) {int parent = (child-1)/2;while (parent >= 0) {if(elem[child] < elem[parent]) {swap(child,parent);child = parent;parent = (child-1)/2;}else {break;}}}public boolean isFull() {return usedSize == elem.length;}

运行结果如下

3.4 删除

  • 将堆顶元素对堆中最后一个元素交换
  • 将堆中有效数据个数减少一个
  • 对堆顶元素进行向下调整

代码示例

java">    public int poll() {if(isEmpty()) {return -1;}int old = elem[0];swap(0,usedSize-1);usedSize--;siftDown(0,usedSize);return old;}public boolean isEmpty() {return usedSize == 0;}

运行结果如下

注意

1. 堆的删除一定删除的是堆顶元素。

2. 对堆进行删除前要判断对是否为空。

3.5 其他方法

1. 获取队顶元素:

代码示例:

java">  public int peek() {return elem[0];}

运行结果如下

2. 获取队列容量

代码示例:

java"> public int size(){return usedSize;}

4. 注意事项

1. 使用时必须导入 PriorityQueue 所在的包,即:
import java . util . PriorityQueue ;
2. PriorityQueue 中放置的 元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出
ClassCastException 异常
3. 不能 插入 null 对象,否则会抛出 NullPointerException
4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
5. PriorityQueue 底层使用了堆数据结构
6.PriorityQueue 默认情况下是小堆 --- 即每次获取到的元素都是最小的元素
本文是作者学习后的总结,如果有什么不恰当的地方,欢迎大佬指正!!!


http://www.ppmy.cn/news/1570215.html

相关文章

基于单片机的智能安全插座(论文+源码)

1 系统整体方案设计 本课题基于单片机的智能安全插座设计&#xff0c;以STM32嵌入式单片机为主体&#xff0c;将计算机技术和检测技术有机结合&#xff0c;设计一款电量参数采集装置&#xff0c;实现电压、电流信号的数据采集任务&#xff0c;电压、电流和功率在上位机的显示任…

中国通信企业协会 通信网络安全服务能力评定 风险评估二级要求准则

通信网络安全服务能力评定要求是对通信网络安全服务单位的资格状况、经济实力、技术能力、服务队伍、服务过程能力等方面的具体衡量和评价。中国通信企业协会通信网络安全服务能力评定风险评估二级应达到风险评估服务一级能力要求的所有条款&#xff0c;并在以下方面增强或增加…

MATLAB实现多种群遗传算法

多种群遗传算法&#xff08;MPGA, Multi-Population Genetic Algorithm&#xff09;是一种改进的遗传算法&#xff0c;它通过将种群分成多个子种群并在不同的子种群之间进行交叉和交换&#xff0c;旨在提高全局搜索能力并避免早期收敛。下面是多种群遗传算法的主要步骤和流程&a…

Android性能调优之需要掌握Dalvik和ART的知识

在Android4.4时ART诞生&#xff0c;DVM和ART在4.4的版本中可以互替&#xff0c;在Android5.0后Android默认运行虚拟机为ART&#xff0c;至此&#xff0c;DVM退出历史舞台。 步入2020年&#xff0c;全球Android用户中&#xff0c;5.0以上的版本占据87~90%&#xff0c;就算DVM已…

在Ubuntu上使用Docker部署DeepSeek

在Ubuntu上使用Docker部署DeepSeek&#xff0c;并确保其可以访问公网网址进行对话&#xff0c;可以按照以下步骤进行&#xff1a; 一、安装Docker 更新Ubuntu的软件包索引&#xff1a; sudo apt-get update安装必要的软件包&#xff0c;这些软件包允许apt通过HTTPS使用存储库…

matlab小波交叉功率谱分析源代码

matlab小波交叉功率谱分析源代码&#xff0c;能够用于计算时间序列之间的相干性 文件列表 wavelet-coherence-master/.gitattributes , 483 wavelet-coherence-master/.gitignore , 1023 wavelet-coherence-master/anglemean.m , 2645 wavelet-coherence-master/ar1.m , 2928…

OpenCV YOLOv11实时视频车辆计数线:让车辆进出有条理!

前言 大家好!今天我们聊个超级有趣的课题——如何用OpenCV结合YOLOv11进行实时视频车辆计数。是不是很炫酷?车辆进出全都清晰可见,连“跑车”都能精确统计!不过,别急,这可不仅仅是数车那么简单,背后还有许多实际问题等着你去搞定,比如计数线、车速、误检这些麻烦的小问…

【漫话机器学习系列】078.如何选择隐藏单元激活函数(How To Choose Hidden Unit Activation Functions)

选择隐藏单元激活函数是神经网络设计中的一个重要步骤&#xff0c;它直接影响到模型的学习能力和训练效果。不同的激活函数具有不同的性质和适用场景&#xff0c;因此在选择时需要根据模型的需求和问题的特性来决定。以下是一些常见的激活函数及其选择依据&#xff1a; 1. Sig…