数据结构——优先级队列(堆)Priority Queue详解

1. 优先级队列

队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该场景下,使用队列不合适

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

2. 优先级队列的模拟实现

2.1 堆的概念

性质:

如上图,堆中某个节点的值总是不大于其父节点的值,称为最小堆/小根堆

堆中某个节点的值总是不小于其父节点的值,称为最大堆/大根堆

堆总是一棵完全二叉树

2.2 堆的存储方式

从堆的概念可知,堆是一棵完全二叉树,因此可用层序的规则采用顺序的方式进行高效存储

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

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

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

2.3 堆的创建

2.3.1 堆向下调整

以集合{ 27,15,19,18,28,34,65,49,25,37 }为例,转换为大根堆的过程:

//TestHeap 类
public class TestHeap {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() {//让parent初始位置在树的最后一棵子树的根节点处,用到公式:根节点 = (i-1)/2//其中usedSize-1表示最后一个节点,从上到下循环遍历整棵树for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {//siftDown方法用来判断树中左右孩子与父亲节点的大小关系siftDown(parent,usedSize);}}//交换函数public 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;//公式:左孩子下标 = 2*根节点下标+1while(child < end) {//end是有效数据个数,不能用<=//下面if走完之后,child一定是左右孩子中最大值的下标if(child+1 < end && elem[child] < elem[child+1]) {child++;}//下面if判断孩子节点是否大于父亲节点,若大于则交换,否则跳出循环,去判断上一棵树if(elem[child] > parent) {swap(child,parent);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.createHeap();}
}

tip:在调整以parent为根的二叉树时,必须要满足parent的左子树和右子树已经是堆,才可以向下调整

时间复杂度:最坏的情况就如上例,从根一路比较到叶子,比较的次数为完全二叉树的高度,即时间复杂度为O(\log _{2}N)

2.3.2 建堆的时间复杂度

推导:

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了方便计算,使用满二叉树来推导

结论: 建堆的时间复杂度为 O(N)

2.4 堆的插入与删除

2.4.1 堆的插入

堆的插入需要两步

1. 先将元素放到树底层最后一个节点(空间不够时扩容)

2. 将插入新节点向上调整,直到满足堆的性质

    public void offer(int val) {if(isFull()) {//判满,若满则扩容elem = Arrays.copyOf(elem,2*elem.length);}elem[usedSize] = val;//val赋值到usedSize位置,usedSize++usedSize++;siftUp(usedSize-1);//向上调整}public void siftUp(int child) {int parent = (child-1)/2;//通过孩子节点找到父亲节点while(parent >= 0) {//当父亲节点下标小于0,说明向上调整结束了,堆已有序if(elem[child] > elem[parent]) {//孩子节点的值大于父亲节点值,即交换swap(child,parent);child = parent;parent = (child-1)/2;}else {//若孩子节点的值小于父亲节点的值,说明堆已有序,直接跳出循环break;}}}public boolean isFull() {return usedSize == elem.length;}

2.4.2 堆的删除

tip:堆的删除一定删除的是堆顶元素,分为三步:

1. 将堆定元素对堆中最后一个元素交换

2. 将堆中有效数据个数减少一个

3. 对堆顶元素进行向下调整

    public int poll() {if(isEmpty()) {//若栈空,返回-1return -1;}int old = elem[0];//记录要删除的数据,最后返回swap(0,usedSize-1);//交换堆顶元素和最后元素usedSize--;//有效个数--siftDown(0,usedSize);//从堆定开始,向下调整return old;}public boolean isEmpty() {return usedSize == 0;}

例题:

1.下列关键字序列为堆的是:()

A: 100,60,70,50,32,65   B: 60,70,65,50,32,100   C: 65,100,70,32,50,60

D: 70,65,100,32,50,60   E: 32,50,100,70,65,60   F: 50,100,70,65,60,32

选A

2.已知小根堆为8,15,10,21,34,16,12,删除关键字8之后需重建堆,在此过程中,关键字之间的比较次数是()

A: 1         B: 2          C: 3        D: 4

解析:选C,15与10比,12与10比,12与16比

3.最小堆[0,3,2,5,7,4,6,8],在删除堆顶元素0之后,其结果是()

A: [3,2,5,7,4,6,8]        B: [2,3,5,7,4,6,8]

C: [2,3,4,5,7,8,6]        D: [2,3,4,5,6,7,8]

选C

3. 常用接口介绍

3.1 PriorityQueue的特性

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

    public static void main(String[] args) {PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();priorityQueue.offer(1);priorityQueue.offer(2);priorityQueue.offer(3);System.out.println(priorityQueue.poll());//运行结果:1System.out.println(priorityQueue.poll());//        2}

关于PriorityQueue的使用注意:

  • 1.使用时必须导入PriorityQueue所在的包,即:import java.util.PriorityQueue;
  • 2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException 异常 
  • 3. 不能插入null对象,否则会抛出 NullPointerException
  • 4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
  • 5. 插入和删除元素的时间复杂度为O(log_2N)
  • 6. PriorityQueue底层使用了堆数据结构
  • 7. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素

3.2 PriorityQueue常用接口介绍

3.2.1 优先级队列的构造

此处只是常见的几种:

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

构造方法原理:

用一个集合来创建优先级队列:

class Student {}
public class Test {public static void main(String[] args) {ArrayList<Integer> arrayList = new ArrayList<>();arrayList.add(1);arrayList.add(2);arrayList.add(3);PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(arrayList);priorityQueue.offer(4);priorityQueue.offer(5);priorityQueue.offer(6);System.out.println(priorityQueue.poll());//运行结果:1System.out.println(priorityQueue.poll());//        2PriorityQueue<Student> priorityQueue1 = new PriorityQueue<>(arrayList);//error//传入参数必须是Student类或其子类}
}

3.2.2 offer元素原理:

查看源码:

结合上面创建小根堆的流程分析:

自己实现比较器:

//实现小根堆的比较器
class IntCmp implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o1.compareTo(o2);//小根堆}
}
public class Test {public static void main(String[] args) {PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new IntCmp());//别忘了此处new比较器对象作为参数priorityQueue.offer(4);priorityQueue.offer(5);priorityQueue.offer(6);System.out.println(priorityQueue.poll());//运行结果:4System.out.println(priorityQueue.poll());//        5}
}//实现大根堆的比较器
class IntCmp implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);//大根堆 两比较器仅有此处不同!!!!!!!!!!!!!!}
}
public class Test {public static void main(String[] args) {PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new IntCmp());priorityQueue.offer(4);priorityQueue.offer(5);priorityQueue.offer(6);System.out.println(priorityQueue.poll());//运行结果:6System.out.println(priorityQueue.poll());//        5}

3.2.3 PriorityQueue的扩容方式

以下是JDK17中的源码:

说明:

如果容量小于64时,按照oldCapacity的2倍方式扩容

如果容量大于等于64,按照oldCapacity的1.5倍方式扩容

如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容

3.3 OJ练习

top-k问题:最大或最小的前k个数据

    public static int[] smallestK(int[] arr, int k) {PriorityQueue<Integer> minHeap = new PriorityQueue<>();for (int i = 0; i < arr.length; i++) {minHeap.offer(arr[i]);}int[] tmp = new int[k];for (int i = 0; i < k; i++) {int val = minHeap.poll();tmp[i] = val;}return tmp;}public static void main(String[] args) {int[] array = {27,15,19,18,28};int[] ret = smallestK(array,3);System.out.println(Arrays.toString(ret));}

运行结果:

虽然上述方法也能满足需求,但是其时间复杂度为O((N+K)*\log _{2}N)

不是非常好的解决方案,现需要一个时间复杂度更小的方法:

若求前K个最小的数字

1. 先把前 个元素建立大小为K的大根堆

2. 遍历剩余 N-K 个元素,若堆顶元素大于当前 i 下标的值就出堆

这样遍历完数组后,大小为 K 的堆中就是最小的 K 个元素

反之求前K个最大的数字就建立小根堆

class IntCmp implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);//大根堆}
}
class Solution {public int[] smallestK(int[] arr, int k) {int[] tmp = new int[k];if(k == 0) {return tmp;}PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new IntCmp());//1.把前K个元素放进堆中for (int i = 0; i < k; i++) {maxHeap.offer(arr[i]);}//2.遍历剩下的 N-k 个元素for (int i = k; i < arr.length; i++) {int val = maxHeap.peek();if(val > arr[i]) {maxHeap.poll();maxHeap.offer(arr[i]);}}//3.将堆中元素放到数组中for (int i = 0; i < k; i++) {tmp[i] = maxHeap.poll();}return tmp;}
}

变种题:求第K小的数据,建立大根堆,堆顶元素就是第K小

3.4 堆排序

要求:在原堆上修改,不能新建

方法两步:

1. 建堆:

  • 要求升序,建大堆
  • 要求降序,建小堆

2. 利用堆删除来进行排序

建堆和堆删除中都用到了向下调整

    //堆排序 升序public void heapSort() {int endIndex = usedSize-1;while(endIndex > 0) {swap(0,endIndex);siftDown(0,endIndex);endIndex--;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/31953.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

odoo 翻译字段sql查询语句

字段写法&#xff1a; name->>en_US 任务&#xff1a; 查询name字段中&#xff0c;包含ring的数据 SQL模糊查询 SELECT * FROM product_public_category WHERE name->>en_US LIKE %ring%; SQL精准查询 SELECT * FROM product_public_category WHERE name->…

深入解析MVC架构(Model-View-Controller Architecture)

目录 前言1. MVC架构概述1.1 模型&#xff08;Model&#xff09;1.1.1 数据管理1.1.2 业务逻辑 1.2 视图&#xff08;View&#xff09;1.2.1 数据展示1.2.2 用户界面设计 1.3 控制器&#xff08;Controller&#xff09;1.3.1 用户输入处理1.3.2 更新模型和视图 2. MVC架构的优缺…

易管理工厂设备日志采集工具

免费试用下载: Gitee下载 最新版本 优势: A. 开箱即用. 解压直接运行.不需额外安装. B. 批管理设备. 设备配置均在后台管理. C. 无人值守 客户端自启动,自更新. D. 稳定安全. 架构简单,内存占用小,通过授权访问.

反激开关电源输出假负载

1、为何需要假负载&#xff1f; 开关电源芯片的占空比最小不可能做到0%&#xff0c;都有一个最小导通时间&#xff0c;不过最小导通时间&#xff0c;在规格书中&#xff0c;不一定给出来 注意&#xff1a;如果没有最小导通时间&#xff0c;就相当于芯片都停止输出了&#xff…

29-Linux--守护进程

一.基础概念 1.守护进程&#xff1a;精灵进程&#xff0c;在后台为用户提高服务&#xff0c;是一个生存周期长&#xff0c;通常独立于控制终端并且周期性的执行任务火处理事件发生 2.ps axj&#xff1a;查看守护进程 3.进程组&#xff1a;多个进程的集合&#xff0c;由于管理…

Flask之模板

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、模板的基本用法 1.1、创建模板 1.2、模板语法 1.3、渲染模板 二、模板辅助工具 2.1、上下文 2.2、全局对象 2.3、过滤器 2.4、测试…

小米测开二面—80min中核

小米测开二面—80min中核 3.28 无自我介绍直接开问&#xff01;你的第一份实习是一个开发工作你的第二实习为什么又跑到测试了你的第一份实习遇到了哪些挑战你的逆向开发的开发目标是什么&#xff0c;使用了什么工具你最终开发落地是用在了什么方面上&#xff0c;比如机器人路…

C语言入门系列:可迁移的数据类型

文章目录 1&#xff0c;精确宽度类型(exact-width integer type)2&#xff0c;最小宽度类型&#xff08;minimum width type&#xff09;3&#xff0c;最快的最小宽度类型&#xff08;fast minimum width type&#xff09;4&#xff0c;可以保存指针的整数类型。5&#xff0c; …

编译 CanMV 固件

前言 上一章节中已经搭建好了基于 CanMV 的 C 开发环境&#xff0c;这么一来便可以进行基于 C 语言和 FreeRTOS 的应用开发或者编译基于 MicroPython 语法的应用开发方式所需的 CanMV 固件&#xff0c;本 章就将带领读者体验一下 CanMV 固件的编译流程。 本章分为如下几个小节&…

Anthropic AI模型Claude 3.5 Sonnet在Amazon Bedrock上正式可用

Claude 3.5 Sonnet是Anthropic最先进的Claude系列AI模型的新成员&#xff0c;比Claude 3 Opus更智能且价格只有其五分之一 北京——2024年6月21日 亚马逊云科技宣布&#xff0c;Anthropic最新、最强大的模型Claude 3.5 Sonnet现已在Amazon Bedrock上正式可用&#xff0c;该模型…

增强-MIGO物料消耗需要将物料描述写到会计凭证的摘要里面

财务比较闲提的需求&#xff0c;有些物料消耗需要将物料描述写到会计凭证的摘要里面&#xff0c; 找了一下增强点&#xff0c;随便搞了一下&#xff0c;可以了。

20240622 每日AI必读资讯

&#x1f916;力压GPT-4o&#xff01;新王Claude 3.5 Sonnet来了&#xff0c;直接免费可用 - 新模型在推理、知识和编码能力评估方面超越了以前的版本和竞争对手GPT 4o模型&#xff0c;同时其运行速度是Claude 3 Opus的两倍。 - 该模型可在http://Claude.ai和Claude iOS应用上…

Spring Bean 生命周期详解

Spring Bean 生命周期详解 在 Spring 框架中&#xff0c;Bean 的生命周期由 Spring 容器全权管理。了解和掌握 Bean 的生命周期对于使用 Spring 开发稳定且高效的应用程序至关重要。本文将详细介绍 Spring Bean 生命周期的五个主要阶段&#xff1a;实例化、属性注入、初始化、…

keepalive+nginx高可用架构

keepalivenginx架构 一.配置真实服务器web1和web2 1.关闭防火墙&#xff0c;并在真实服务器下载http服务 [rootlocalhost ~]# systemctl stop firewalld.service [rootlocalhost ~]# setenforce 0 [rootlocalhost ~]# yum install httpd -y 2.分别在web1和web2上制作网页…

【Redis】List的常用命令以及常用场景

Redis List 是一个简单的链表&#xff0c;支持在两端进行插入和删除操作。这种数据结构在许多场景下非常有用&#xff0c;例如任务队列、消息队列等。Redis 提供了一系列针对 List 的操作命令&#xff0c;帮助我们更高效地操作链表。 1. List常用命令 操作类型命令时间复杂度…

Ubuntu系统使用快速入门实践(八)—— git 命令使用

Ubuntu系统使用快速入门实践系列文章 下面是Ubuntu系统使用系列文章的总链接&#xff0c;本人发表这个系列的文章链接均收录于此 Ubuntu系统使用快速入门实践系列文章总链接 下面是专栏地址&#xff1a; Ubuntu系统使用快速入门实践系列文章专栏 文章目录 Ubuntu系统使用快速…

JupyterLab使用指南(八):更改JupterLab左侧默认打开目录

在JupyterLab中&#xff0c;默认打开路径通常是由其配置文件中的root_dir设置决定的。如果你没有特意设置这个配置项&#xff0c;JupyterLab可能会使用当前用户的主目录或者上一次关闭时的路径作为默认打开路径。 更改JupyterLab默认路径的操作在不同操作系统下大体相似&…

windows使用curl命令出现乱码的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

海口注册公司代理记账的服务优势与流程解析

在海口注册公司加入代理记账服务有多种优势。代理记账公司提供专业的财务服务&#xff0c;帮助企业节约成本、提高效率&#xff0c;实现财务管理的合规性。以下是代理记账服务的主要优势和流程解析&#xff1a; https://www.9733.cn/news/detail/173.html 一、代理记账服务的…

分布式光纤测温DTS在工程现场中稳定性与可靠性如何?

20年前&#xff0c;分布式光纤测温(Distributed Temperature Sensing&#xff0c;DTS)技术的发展尚不成熟&#xff0c;设备成本高昂&#xff0c;其稳定性与可靠性也存在一定问题。然而&#xff0c;经过二十多年的不断发展与创新&#xff0c;DTS技术在工程现场应用中取得了显著进…