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

【数据结构】 优先级队列 —— 堆

【数据结构】 优先级队列 —— 堆

  • 一、优先级队列
    • 1.概念
  • 二、优先级队列的模拟实现
    • 2.1 堆的概念
    • 2.2 堆的性质
    • 2.3 堆的存储方式(顺序存储 --> 存储到数组里)
    • 2.4 堆的创建(以向下调整,创建大根堆为例)
    • 2.5 堆的插入与删除
      • 2.5.1 堆的插入(向上调整)
      • 2.5.2 堆的删除
    • 2.6 用堆模拟实现优先级队列
  • 三、PriorityQueue常用接口介绍
    • 3.1 PriorityQueue的特性
    • 3.2 PriorityQueue常用接口
      • 3.2.1 优先级队列的常用构造方法
  • 3.2 插入 / 删除 / 获取优先级最高的元素
  • 四、堆的应用
    • 4.1 PriorityQueue的实现
    • 4.2 堆排序
      • 4.2.1 建堆
      • 4.2.2 利用堆删除思想来进行排序
    • 4.3 堆相关的 oj题
      • 4.3.1 Top-k问题
      • 4.3.2 堆排序 —— 对数据,从小到大排序
    • 4.4 堆相关的练习

一、优先级队列

1.概念

前⾯学习过队列,队列是⼀种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,⼀般出队列时,可能需要优先级高的元素先出队列,在下面的场景中,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。

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

二、优先级队列的模拟实现

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

2.1 堆的概念

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

2.2 堆的性质

  • 堆中某个节点的值总是不大于于或不小于其父节点的值;
  • 堆总是⼀棵完全二叉树。
    在这里插入图片描述
    在这里插入图片描述

2.3 堆的存储方式(顺序存储 --> 存储到数组里)

从堆的概念可知,堆是一颗完全二叉树,因此可以层序的规则使用顺序的方式来高效存储
在这里插入图片描述

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


将元素存储到数组中后,可以根据【数据结构】 二叉树 中 2.3二叉树的性质5对树进行还原。假设 i 为节点在数组中的下标,则有:

在这里插入图片描述

2.4 堆的创建(以向下调整,创建大根堆为例)

对于集合{ 27,15,19,18,28,34,65,49,25,37 }中的数据,如果将其创建成大根堆呢?

(1)手动调整 完全二叉树到大根堆 图解
在这里插入图片描述在这里插入图片描述


(2)Java 代码实现 大根堆 思路
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(3)Java 代码实现(向下调整)

TestHeap 类:

//向下调整 创建大根堆
public class TestHeap {//存储数据public int[] elem;//usedSize: 存放有效数据的个数,假如数组长度为10,但只存了3个public int usedSize;public TestHeap(){elem = new int[10];}public void init(int[] array){for (int i = 0; i < array.length; i++) {elem[i] = array[i];usedSize++;}}//创建大根堆public void creatHeap(){for (int parent =(usedSize-1-1)/2;parent >= 0; parent--) {//从每颗子树开始,向下调整shiftDown(parent,usedSize);}}private void shiftDown(int parent, int usedSize) {int child = 2 * parent + 1;//还没有调整完毕while(child < usedSize){if(child+1 < usedSize && elem[child] < elem[child+1]){//创建小根堆,条件则为://if(child+1 < usedSize && elem[child] > elem[child+1])child++;}//走到这里,child下标 一定是左右孩子最大值的下标if(elem[child] > elem[parent]){//创建小根堆,条件则为://if(elem[child] < elem[parent])int tmp = elem[child];elem[child] = elem[parent];elem[parent] = tmp;parent = child;child = 2 * parent + 1;}else{break;}}}
}

Test 类:

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();testHeap.init(array);testHeap.creatHeap();System.out.println("========");}
}

输出结果:

在这里插入图片描述

(4)堆向下调整 的时间复杂度:O(n)

在这里插入图片描述

2.5 堆的插入与删除

2.5.1 堆的插入(向上调整)

(1)图解

在这里插入图片描述
在这里插入图片描述
(2)Java 代码实现

TestHeap 类:

private void swap(int parent, int child) {int tmp = elem[child];elem[child] = elem[parent];elem[parent] = tmp;}//向上调整 堆的插入/*** 插入数据:* 每次插入到当前堆的最后一个(数组的最后一个),然后向上调整* @param val*/public void offer(int val){if(isFull()){elem = Arrays.copyOf(elem,2*elem.length);}elem[usedSize] = val;//调整shiftUp(usedSize);usedSize++;}private void shiftUp(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 elem.length == usedSize;}

输出结果:

在这里插入图片描述

2.5.2 堆的删除

(1)图解
在这里插入图片描述
在这里插入图片描述
(2)Java 代码实现

private void swap(int i, int j) {int tmp = elem[i];elem[i] = elem[j];elem[j] = tmp;}//向上调整 堆的插入/*** 插入数据:* 每次插入到当前堆的最后一个(数组的最后一个),然后向上调整* @param val*/public void offer(int val){if(isFull()){elem = Arrays.copyOf(elem,2*elem.length);}elem[usedSize] = val;//调整shiftUp(usedSize);usedSize++;}private void shiftUp(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 elem.length == usedSize;}//删除堆public int poll(){if(isEmpty()){//自己抛出异常return -1;}int oldValue = elem[0];swap(0,usedSize-1);//调整0下标的树usedSize--;shiftDown(0,usedSize);return oldValue;}public boolean isEmpty(){return usedSize == 0;}

Test 类:

testHeap.poll();System.out.println("========");

输出结果:

在这里插入图片描述

2.6 用堆模拟实现优先级队列

public class MyPriorityQueue {// 演⽰作⽤,不再考虑扩容部分的代码private int[] array = new int[100];private int size = 0;public void offer(int e) {array[size++] = e;shiftUp(size - 1);}public int poll() {int oldValue = array[0];array[0] = array[--size];shiftDown(0);return oldValue;}public int peek() {return array[0];}}

三、PriorityQueue常用接口介绍

3.1 PriorityQueue的特性

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

在这里插入图片描述

import java.util.LinkedList;
import java.util.PriorityQueue;class Student{}public class Test {public static void main(String[] args) {PriorityQueue<Student> queue = new PriorityQueue<>();
/*//1. PriorityQueue中放置的元素必须要能够⽐较⼤⼩,// 不能插⼊⽆法⽐较⼤⼩的对象,否则会抛出ClassCastException异常queue.offer(new Student());//error*//*//2.不能插⼊null对象,否则会抛出NullPointerExceptionqueue.offer(null);//error*///LinkedList 可以存nullLinkedList<Student> queue1 = new LinkedList<>();queue1.offer(null);queue1.offer(null);System.out.println(queue1.size());//2}}

在这里插入图片描述

3.2 PriorityQueue常用接口

3.2.1 优先级队列的常用构造方法

在这里插入图片描述

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.PriorityQueue;class Student{}public class Test {public static void main(String[] args) {//PriorityQueue(): 创建⼀个空的优先级队列,底层默认容量是11PriorityQueue<Student> queue = new PriorityQueue<>();//PriorityQueue(int intitialCapacity)//创建⼀个空的优先级队列,底层的容量为initialCapacityPriorityQueue<Student> queue2 = new PriorityQueue<>(15);// ⽤ArrayList对象来构造⼀个优先级队列的对象// queue3中已经包含了三个元素ArrayList<Integer> list = new ArrayList<>();list.add(1);list.add(10);list.add(5);PriorityQueue<Integer> queue3 = new PriorityQueue<>(list);System.out.println("=======");}}

注意:默认情况下,PriorityQueue队列是⼩堆,如果需要⼤堆需要⽤⼾提供⽐较器。


下面的代码创建出来的就是⼀个大堆。

import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.PriorityQueue;// ⽤⼾⾃⼰定义的⽐较器:直接实现Comparator接⼝,然后重写该接⼝中的compare⽅法即可class ImpLess implements Comparator<Integer>{public int compare(Integer o1,Integer o2){return o1.compareTo(o2);//小根堆}
}class ImpBig implements Comparator<Integer>{public int compare(Integer o1,Integer o2){return o2.compareTo(o1);//大根堆}
}public class Test {public static void main(String[] args) {PriorityQueue<Integer> queue =  new PriorityQueue<>(new ImpBig());queue.offer(4);queue.offer(3);queue.offer(2);queue.offer(1);queue.offer(5);System.out.println(queue.peek());}

3.2 插入 / 删除 / 获取优先级最高的元素

在这里插入图片描述

代码示例:

在这里插入图片描述
在这里插入图片描述

四、堆的应用

4.1 PriorityQueue的实现

堆排序即利⽤堆的思想来进⾏排序,总共分为两个步骤:
(1)建堆。
(2)利用堆删除思想来进行排序。

4.2 堆排序

在这里插入图片描述

4.2.1 建堆

  • 升序:创建大堆
  • 降序:创建小堆

4.2.2 利用堆删除思想来进行排序

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

具体详见本篇的 2.4 堆的创建 和 2.5 堆的插入与删除。

4.3 堆相关的 oj题

4.3.1 Top-k问题

TOP-K问题:即求数据集合中前K个最⼤的元素或者最⼩的元素,⼀般情况下数据量都⽐较⼤。

(1)LC面试题17.14. 最小K个数

在这里插入图片描述

(2)图解

在这里插入图片描述

(3)Java 代码实现

解法1:

public class Test {/*** 解法1:不算是Topk 真正的解法* 时间复杂度太高* @param array* @param k* @return*/public static int[] topk1(int[] array,int k){PriorityQueue<Integer> queue = new PriorityQueue<>();//O(n*log2N)for (int i = 0; i < array.length; i++) {queue.offer(array[i]);}//O(k*logN)int[] ret = new int[k];for (int i = 0; i < k; i++) {ret[i] = queue.poll();}return ret;}public static void main(String[] args) {int[] array = {1,12,3,41,5,16,7,81,9,10};int[] ret = topk1(array,3);System.out.println(Arrays.toString(ret));//[1, 3, 5]}

解法3:

 //TopK问题 解法3class ImpBig implements Comparator<Integer>{public int compare(Integer o1,Integer o2){return o2.compareTo(o1);//大根堆}}class Solution {public static int[] smallestK(int[] array,int k){int[] ret = new int[k];if(k <=0 ){return ret;}PriorityQueue<Integer> queue = new PriorityQueue<>(new ImpBig());//O(k*logN)+O(N-K)*logK = O(N*logK)for (int i = 0; i < k; i++) {queue.offer(array[i]);}//从第K+1个元素,开始遍历: O(N-K)*logKfor (int i = k; i < array.length; i++) {//先获取堆顶元素int val = queue.peek();if(val > array[i]){queue.poll();queue.offer(array[i]);}}//走到这里,队列里面就是前K个最小的元素for (int i = 0; i < k; i++) {ret[i] = queue.poll();}return ret;}
}

【TopK问题总结】

1. 找前K个最小的数据 / 找到第K小的的元素 —— 建立大根堆
2. 找前K个最大的数据 / 找到第K大的的元素—— 建小根堆

4.3.2 堆排序 —— 对数据,从小到大排序

对于集合{ 27,15,19,18,28,34,65,49,25,37 } 中的数据,如果将其从小到大排序呢?

(1)图解

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(2)Java 代码实现

    //对数据,从小到大排序//时间复杂度:堆排序 O(N+log2N)//空间复杂度:O(1)public void heapSort(){int end = usedSize - 1;while(end > 0){swap(0,end);shiftDown(0,end);end--;}}

输出结果:

在这里插入图片描述

4.4 堆相关的练习

在这里插入图片描述

相关文章:

  • 【MySQL进阶】MySQL程序
  • TrOCR模型微调
  • LDStega论文阅读笔记
  • 阿里云可观测 2025 年 5 月产品动态
  • 【每日likou】704. 二分查找 27. 移除元素 977.有序数组的平方
  • docker-compose搭建eureka-server和zipkin
  • asio之静态互斥量
  • ubuntu22 arm 编译安装input leap
  • 20250611让NanoPi NEO core开发板在Ubuntu core16.04系统下开机自启动的时候拉高GPIOG8
  • NumPy 2.x 完全指南【二十五】记录数组
  • 建站新手:我与SiteServerCMS的爱恨情仇(三)
  • 【c++八股文】Day2:虚函数表和虚函数表指针
  • RPC启动机制及注解实现
  • day 50
  • 0:0 error Parsing error: Cannot read properties of undefined (reading ‘map‘)
  • Rust 学习笔记:通过异步实现并发
  • C语言学习20250611
  • 亮数据抓取浏览器,亚马逊数据采集实战
  • Flask 报错修复实战:send_file() got an unexpected keyword argument ‘etag‘
  • vite原理
  • 专业企业网站开发公司/电商平台有哪些
  • 涞水住房和城乡建设委员会网站/百度关键词排名
  • 遵义本地网络平台/河北seo基础知识
  • ppt做杂志模板下载网站有哪些/网页优化
  • 网站建设 模版选择中心/互联网销售公司
  • 浙江网站建设推广/百度sem推广具体做什么