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

堆(Heap)优先级队列(Priority Queue)

一、堆的概念

堆(Heap)是一种特殊的基于树的数据结构,通常分为最大堆和最小堆两种类型。它满足堆属性:对于最大堆,父节点的值总是大于或等于其子节点的值;而对于最小堆,父节点的值总是小于或等于其子节点的值。

数组实现:虽然堆是一个基于树的结构,但它通常用数组来实现。对于位于数组索引i处的元素,其左孩子的索引是2*i + 1,右孩子的索引是2*i + 2,而其父节点的索引是(i-1)/2

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质: 堆中某个节点(孩子节点)的值总是不大于或不小于其父节点的值;

堆总是一棵完全二叉树

2.堆得存储方式

二叉树层序遍历存储在数组中

1)堆的创建(大根堆)

向下调整法 (同理可创建小根堆,孩子中最小的与根节点交换)

 完全二叉树从最后一棵子树慢慢调整为堆,最后一课子树根节点与左右孩子最大值交换(根节点<左/右孩子).然后记得递归调整堆结构被破坏的子树

package org.example;import java.util.Arrays;public class TestHeap {private int[] elem;private int usedsize;public  TestHeap(int capacity){this.elem=new int[capacity];}//初始化堆public  void initHeap(int []array){for (int i = 0; i < array.length ; i++) {elem[i]=array[i];usedsize++;//数组堆元素个数}}public void display(){System.out.println(Arrays.toString(elem));}//创建堆//一棵子树有孩子一定先有左孩子 左孩子(usedsize-1-1)/2->父亲节点public void createHeap(){//从最后一棵子树根节点按个遍历每棵树根节点for(int parent=((usedsize-1-1)/2);parent>=0;parent--){shiftDown(parent,usedsize);}}public void shiftDown(int parent,int usedsize){int child=(2*parent)+1;//左孩子节点下标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;}}}public void swap(int i,int j){int tmp=elem[i];elem[i]=elem[j];elem[j]=tmp;}}

Test测试类

package org.example;import java.util.Arrays;public class Test {public static void main(String[] args) {int []array={27,15,19,18,28,34,65,49,25,37};TestHeap testheap=new TestHeap(array.length);testheap.initHeap(array);testheap.createHeap();testheap.display();}
}

 

创建前

创建后(大根堆)

 2)堆的插入

向上调整(先将新插入的节点放到堆的末尾,最后一个孩子之后,堆的结构性质被破坏,在慢慢往上移,向上调整为大/小根堆)

注意:空间放满时需要扩容

 public void offer(int val){if(isfull()){this.elem=Arrays.copyOf(elem,2*elem.length);//向上调整}//添加的元素是新的孩子this.elem[usedsize]=val;shiftUp(usedsize);usedsize++;}//向上调整函数 (也创建堆)//向下调整方法比向上调整方法少遍历(最后)一层节点public void  shiftUp(int child){int parent=(child-1)/2;while (child>0){if(elem[child]>elem[parent]){swap(child,parent);child=parent;parent=(child-1)/2;}else {break;}}}public  boolean isfull(){return elem.length==usedsize;}

main方法

int []array={27,15,19,18,28,34,65,49,25,37};TestHeap testheap=new TestHeap(array.length);testheap.initHeap(array);testheap.createHeap();//testheap.display();testheap.offer(88);//插入元素打印testheap.display();

3)堆的删除

删除的一定是堆顶(根节点元素)

a.将堆顶元素与最后一个孩子元素交换

b.将堆中有效元素的个数减少一个

c.将堆顶元素向下调整,满足堆特性为止

    public int pool(){//一定是删除堆顶元素int tmp= elem[0];//保留删除的元素//交换元素swap(0,usedsize-1);usedsize--;shiftDown(0,usedsize);//返回被删除的元素return tmp;}
 int val=testheap.pool();System.out.println("被删除的元素是"+val);//删除后元素打印testheap.display();

    public void display(){// System.out.println(Arrays.toString(elem));//显示堆的有效部分System.out.println(Arrays.toString(Arrays.copyOf(elem, usedsize)));}

打印方法display()修改原因

你遇到的问题是:

> 删除堆顶元素(65)后,65 **仍然出现在数组中**,只是被“挤”到了最后面,数组并没有真正缩小,65 依然存在。

---

## ✅ 你的代码是**逻辑正确的**,但你对 `display()` 方法的理解有误。

---

## 🧠 堆的底层实现是一个数组,但它**不是动态收缩的数组**,而是通过 `usedsize` 来控制堆的有效元素数量。

你调用 `pool()` 方法后:

```java
usedsize--;
```

这意味着堆的有效长度减少了 1,但底层数组并没有变小,**只是堆不再“关心”最后一个元素**。

---

## 🔍 举个例子

你原来的堆是:

```
[65, 49, 34, 25, 37, 27, 19, 18, 15, 28]
```

调用 `pool()` 后,堆会执行:

```java
swap(0, usedsize - 1); // 交换 65 和 28
usedsize--;            // usedsize 从 10 变成 9
shiftDown(0, usedsize); // 从新的堆顶 28 开始下滤
```

此时数组变成:

```
[49, 37, 34, 25, 28, 27, 19, 18, 15, 65]
```

但是 `usedsize = 9`,意味着堆只“看到”前 9 个元素:

```
[49, 37, 34, 25, 28, 27, 19, 18, 15]
```

而 `65` 被留在数组最后,**只是不再属于堆的范围**。

---

## ✅ 所以你看到的输出:

```
[49, 37, 34, 25, 28, 27, 19, 18, 15, 65]
```

这是完全正常的,因为:

- `display()` 方法打印的是整个数组(包括不属于堆的部分)
- 你没有重新拷贝数组或清空无效数据

---

## ✅ 如何正确打印堆的有效部分?

修改 `display()` 方法为只打印有效部分:

```java
public void display() {
System.out.println(Arrays.toString(Arrays.copyOf(elem, usedsize)));
}
```

## ✅ 修改后输出:

调用 `testheap.display();` 会输出:

```
49 37 34 25 28 27 19 18 15
```

二、优先级队列

队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队 列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如 果有来电,那么系统应该优先处理打进来的电话;

数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数 据结构就是优先级队列(Priority Queue)。

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

Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线 程不安全的,PriorityBlockingQueue是线程安全的

1)优先级队列使用

导包

import java.util.PriorityQueue;

2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException异常

3. 不能插入null对象,否则会抛出NullPointerException

4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容

5. 插入和删除元素的时间复杂度为

6. PriorityQueue底层使用了堆数据结构

7. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素

public class Test {public static void main(String[] args) {PriorityQueue<Integer> priorityQueue=new PriorityQueue<>();priorityQueue.offer(10);priorityQueue.offer(4);priorityQueue.offer(8);priorityQueue.offer(3);priorityQueue.offer(7);System.out.println(priorityQueue.poll());System.out.println(priorityQueue.poll());System.out.println(priorityQueue.poll());System.out.println(priorityQueue.poll());System.out.println(priorityQueue.poll());}

打印结果证明是小根堆

 2)优先级队列的创建方式(3种)

static void TestPriorityQueue(){
// 创建一个空的优先级队列,底层默认容量是11
PriorityQueue<Integer> q1 = new PriorityQueue<>();
// 创建一个空的优先级队列,底层的容量为initialCapacity
PriorityQueue<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());
}

3)优先级队列的使用方法

函数名                                  功能介绍

boolean offer(E e)    插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时间复杂度 log2N,注意:空间不够时候会进行扩容

E peek() 获取优先级最高的元素,如果优先级队列为空,返回null

E poll() 移除优先级最高的元素并返回,如果优先级队列为空,返回null

int size() 获取有效元素的个数

void clear() 清空

boolean isEmpty() 检测优先级队列是否为空,空返回true

http://www.lryc.cn/news/600007.html

相关文章:

  • python基础:request模块简介与安装、基本使用,如何发送get请求响应数据,response属性与请求头
  • 《计算机组成原理与汇编语言程序设计》实验报告一 基本数字逻辑及汉字显示
  • 机器学习详解(28):LightGBM原理
  • Linux系统编程——进程
  • 腾讯云CodeBuddy+微信小程序:5分钟开发番茄小闹钟
  • IPv6,你开始使用了吗?
  • 学习日志18 python
  • 从入门到进阶:JavaScript 学习之路与实战技巧
  • [科普] 快速傅里叶变换(FFT)和离散傅里叶变换(DFT)的差异
  • JDK8保姆级安装教程
  • 微观低代码
  • 前端项目组成
  • 【DeepRare】疾病识别召回率100%
  • Linux parted问题:指定分区边界失效
  • HTTP响应状态码详解
  • Java 常用数据库详解
  • 数据结构 之 【排序】(递归实现快速排序)
  • 【补题】Codeforces Round 735 (Div. 2) B. Cobb
  • 中国移动融合企业网关H10G-13-中星微ZX296716处理器-破解教程
  • 基于springboot的小区车位租售管理系统
  • 学习:JS[6]环境对象+回调函数+事件流+事件委托+其他事件+元素尺寸位置
  • 利用DeepSeek测试kdb+x的tpch sf=10数据
  • Vue2-VueRouter
  • rtpengine的docker化
  • 【C语言进阶】一篇文章教会你文件的读写
  • 微服务架构中的资源调度与负载均衡实践
  • CSS3新特性深度解析:Position Sticky粘性定位完整指南
  • Android 15中的16KB大页有何优势?
  • 深度学习篇---预训练模型
  • 升级目标API级别到35,以Android15为目标平台(三 View绑定篇)