Java:优先级队列(堆)

一、初识【堆】

1、什么是【优先级队列】?

  前面的文章我们介绍过队列,队列是一种先进先出的数据结构,但是,在某些情况下,操作的数据可能需要有一个优先级来获取数据,例如优先获取队列中最大的元素,或者优先获取队列中最小的元素,单靠先进先出并无法实现这种功能,在该种情况下,使用队列明显就不合适了。

  举例来说,当一个队列分别存储11,13,23,53时,如果我们要获取队列中的最大元素,那么根据队列的规则,只能先分别在队列中弹出11,13,23后才可以获取到53。

  因此,我们由此需要一个优先级队列来实现这个功能!

2、堆的底层实现

  优先级队列有个别称,也就是【堆】,堆是由一个类实现的:PriorityQueue类(这个类实现了Queue接口)

  在学习堆之前,我们需要学习二叉树,因为它的底层就是一个二叉树,并且这棵二叉树是一颗完全二叉树!另外,这个二叉树又是使用数组模拟实现的!关于二叉树的学习我也有写过相关文章,如有兴趣可去了解!

3、堆的概念

 堆分为【大根堆】和【小根堆】!

小根堆:

所谓的小根堆,其实就是根节点的大小  小于   孩子节点的大小

整棵树都是【小根堆】的前提是:每棵子树都是【小根堆】

大根堆: 

所谓的大根堆,其实就是根节点的大小  大于   孩子节点的大小

整棵树都是【大根堆】的前提是:每棵子树都是【大根堆】

4、节点下标的规律

如图:每个节点上面的数字代表节点的下标

假设一个节点的下标为i

1、如果  i  =0;则 i 表示的节点为根节点,如果该节点不是根节点,则节点  i  的父亲节点下标为(i-1)/2

2、如果2*i +1 小于 节点的个数,则节点 i 的左孩子下标为  2* i+1;否则没有左孩子

3、如果2*i  +2 小于 节点的个数,则节点 i 的右孩子下标为2* i +2,否则没有右孩子 



二、模拟实现【堆】

首先我们先创建两个类,TestHeap类(模拟实现堆这个类)和  Test类(测试类)

1、TestHeap类的一些【基础对象】和【方法】

首先,我们要实现的堆是通过数组实现的,因此这个TestHeap的类要【创建一个数组对象】,另外,我们还需要实现一下【构造方法】 和 【初始化数组的方法】



2、实现大根堆方法

前面我们介绍过【大根堆】的性质,它需要满足两个条件:

1、根节点元素【大于】孩子节点元素,这种树称为【大根堆】

2、整棵树是大根堆的前提是:每一颗子树都是大根堆

【问题】

  那么,如果给你一个乱序的数组,如何排列数组元素,使这个数组的【逻辑结构】是【二叉树的大根堆】呢?(如果不了解什么是逻辑结构,那么一定是你看漏了,它在这篇文章【堆的概念】图中)

  其实不难,首先我们先找到这颗满二叉树的最后一颗子树的父亲节点下标parent(以下简称parent为P),在根据【节点下标规律】求出该父亲节点左右孩子的节点下标,找到孩子节点元素的最大值,同父亲节点元素比较大小,接着会出现以下两种情况:

1:如果父亲节点元素【大于或等于】孩子节点的最大值,那么不做任何操作

2:如果父亲节点元素【小于】孩子节点的最大值,那么【交换】父亲节点该孩子节点的元素,接下来操作才是难点:

  在交换完节点内容后,则有可能会出现,交换前【孩子节点树】本来已经调整为【大根堆】,交换后又不满足【大根堆的性质】,那么就要【继续调整】该孩子节点树,使其满足大根堆的性质!(看到这里如果不理解没有关系,后举一个例子,你也许会恍然大悟)

【例子】

  如图,此时这个二叉树其实是一个数组的逻辑结构图!因为该数组尚未排序,因此其不满足大根堆的性质,圆圈里面的数代表【元素的值】,圆圈下面的数子代表【元素的下标】

1、  首先,我们之前在TestHeap类中定义了【usedSize】成员变量,用来记录数组存储的元素个数。关于如何调整该堆?先从最后一颗子树,【从右到左,从下到上】的轨迹依次调整每一颗子树,使其都成为【大根堆】。

  所以,我们需要求出最后一颗子树P的节点下标,即元素为4,元素下标为3的元素! 它就是我们要调整的第一棵树!

   那么该如果求得该元素下标的值?其实我们早就知道了:P=(最后一个元素的下标-1)/2,也就是P=[  (usedSIze-1) - 1  ] / 2;

求得该P节点的孩子节点最大值为9,因此交换元素

2、  第一棵树调整完成,开始调整第二棵树,即元素为3,下标为2的元素 ,这个时候使P=P-1即可;

求得该P节点的孩子节点的最大值为7,交换元素

3、第二棵树调整完成,开始调整第三棵树,即元素为2,下标为1的元素,P=P-1; 

此时,发现P节点的孩子节点最大值为9,交换元素;交换完元素我们就会发现,第一棵树交换前是大根堆,交换后就不是大根堆了,因此,我们需要再次调整这棵树;

令P=该孩子节点的下标,重新调整!

  后面的情况就不一一在推导了,相信大家也可以自己推导出来!上面的讲解只是未来让你能更好地结合讲解理解代码,大根堆的代码实现如下! 

它分为三个方法:

第一个方法:createHeap方法利用while循环,依次调整每一颗子树(具体调整调用方法三),是实现数组调整的主体逻辑

第二个方法:swap方法即交换父亲节点和孩子节点元素

第三个方法:siftDown方法:则通过接收根节点的下标,调整以该节点为下标的整棵树,使其成为大根堆!creatHeap方法配合使用该方法,完成每一颗子树的调整,使整棵树成为大根堆!

    //实现大根堆方法:public void createHeap(){int parent=(usedSize-1-1)/2;for(int i=parent;i>=0;i--){siftDown(i,usedSize);//调用向下调整方法}}//交换数组元素方法:private void swap(int i,int j){int tmp=elem[i];elem[i]=elem[j];elem[j]=tmp;}//向下调整方法:private void siftDown(int parent,int end){//通过父亲节点的下标计算左孩子节点下标int child=2*parent+1;//当孩子节点的下标大于数组下标的最大值,跳出循环while(child<end){//确保child为最大孩子节点的下标if(child+1<end&&elem[child]<elem[child+1]){child++;}//调整为大根堆if(elem[parent]<elem[child]){swap(child,parent);//调用交换数组元素方法parent=child;child=2*parent+1;}else{break;}}}


3、添加元素方法:

在给这个数组添加元素的时候,由于数组的【逻辑结构】要满足大根堆的性质,因此,在添加完元素过后仍然需要检查一下该数组的元素顺序。

如图为例:

  以下是一个排序为大根堆的数组的【逻辑结构】(黄色图标元素80为我们要添加的元素),现在我们要给该数组末尾太添加一个元素80,添加80后,我们发现这棵二叉树不满足大根堆的结构了!因此,我们需要对此做出调整!

调整原理!

首先,我们调整的主体逻辑是【向上调整】,即从最下面的子树开始,依次向上调整。具体实现如下:

1、先创建一个siftUp(int child)方法,给该方法传入数组最后一个元素的下标(以下简称孩子节点下标为C),此时这个下标正指向二叉树的最后一棵树的孩子节点,接着计算出父亲节点的下标(以下简称父亲节点下标为P);

比较父亲节点和孩子节点元素的大小,发现孩子节点的元素【大于】父亲节点的元素,交换元素;

将P的值【赋值】给C,即C=P,使P指向父亲节点的父亲节点的下标,即P=(C-1) / 2;

2、比较父亲节点和孩子节点元素的大小,发现父亲节点的元素【小于】孩子节点的元素 ,交换元素;

接着C=P,P=(C-1) /  2;

3、比较父亲节点和孩子节点元素的大小,发现父亲节点的元素【小于】孩子节点的元素 ,交换元素;

接着C=P,P=(C-1) /  2;

发现此时P<0,因此调整结束!

代码实现:

 //插入新数据方法:public void offer(int val){//1、如果数组满了,扩容if(isFull()){//调用判断数组空间已满的方法elem=Arrays.copyOf(elem,2*elem.length);}//2、添加元素elem[usedSize]=val;usedSize++;//3、调整数组顺序使其插入新数据后仍然满足大根堆siftUp(usedSize-1);}//判断数组空间是否已满的方法private boolean isFull(){return usedSize== elem.length;}//向上调整方法private  void siftUp(int child){int parent=(child-1)/2;while(parent>=0){if(elem[child]>elem[parent]){swap(child,parent);//使child指向该孩子节点的父亲节点child=parent;//计算出该父亲节点的父亲节点下标parent=(child-1)/2;}else{break;//注意注意注意!}}}

 在这里有一个点需要注意,那就是siftUp方法循环里面的else语句的作用,让我来举一个例子;

假设这里我们要添加的元素不是80,而是8;

此时if……else……语句走else语句,跳出while循环,因为此时这个二叉树添加8这个元素后仍然是大根堆,不需要调整!



4、删除元素方法: 

  【优先级队列】删除元素时,是删除二叉树的根节点元素,因为该元素是整个数组中的【最大值】或者【最小值】,那么该如何执行该操作?

  只需要将【数组第一个元素】和【数组最后一个元素】交换,usedSize--,最后对【下标为0】的树进行一次向下调整即可!

1、

2、 

代码实现: 

  //删除元素方法:public int  poll(){//如果数组为空,返回-1if(isEmpty()){return -1;}//数组不为空,执行删除操作int old=elem[0];swap(0,usedSize-1);//交换数组第一个元素和最后一个元素的值usedSize--;siftDown(0,usedSize);//向下调整第一棵树return old;}//判断数组是否为空方法public boolean isEmpty(){return usedSize==0;}
}



三、PriorityQueue的常见接口介绍

1、优先级队列的构造

priorityQueue的构造方法有三种:

构造方法:                                  功能介绍:
PriorityQueue()                            创建一个空的优先级队列,默认容量是11
PriorityQueue(int initialCapacity)         创建一个初始容量为initialCapacity的优先级队列
PriorityQueue(Collection<? extends E> c)   用一个集合来创建优先级队列

 实例:

 //创建一个空的优先级队列,底层默认容量为11PriorityQueue<Integer> q1=new PriorityQueue<>();//创建一个空的优先级队列,底层的容量为initialCapacityPriorityQueue<Integer> q2=new PriorityQueue<>(100);//以一个集合为参数ArrayList<Integer> list=new ArrayList<>();PriorityQueue<Integer> q3=new PriorityQueue<>(list);


2、比较器

  其实,PriorityQueue默认情况下的优先级队列是【小根堆】, 让我们来看看!

首先,创建一个优先级队列q1,给该队列添加两个元素,分别是:2 、 3

如果该队列是小根堆,那么会打印出来2;反之,如果该队列是大根堆,那么会打印出来3;

运行代码,发现打印出来的是2,说明该队列默认是小根堆! 

那么,就有一个问题,如果我们要使该队列是一个大根堆,该怎么做呢?

答:在构造方法中传入一个【比较器】!

//自定义实现一个【比较器】
class IntCmp 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> q1=new PriorityQueue<>(new IntCmp());q1.offer(2);q1.offer(3);System.out.println(q1.poll());}
}

 

 

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

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

相关文章

【linux】动静态库的使用与制作

本章节是基础IO的的最后一个话题!! 目录 浅谈一下动静态库&#xff1a;动静态库的制作与使用&#xff1a;静态库&#xff1a;怎么办&#xff1a;方法一&#xff1a;方法二&#xff1a;方法三&#xff1a;方法四&#xff1a; 是什么&#xff1a;为什么&#xff1a; 动态库&#…

YOLOv8-pose针对视频实时提取打印对应关节点序号及坐标

因为我在找如何提取YOLOv8-pose的关键点的时候&#xff0c;大多都是针对静态图像&#xff0c;视频直接套用不太行&#xff0c;因此就改进了一下&#xff0c;如下&#xff1a; 初步代码&#xff1a; import torch # 导入PyTorch库 import cv2 as cv # 导入OpenCV库并重命名为…

同态加密原理解析

目录 1.数学介绍2.使用多项式环进行加密2.1 私钥和公钥的产生2.2 加密2.3 解密 3.同态计算3.1 同态加法3.2 同态乘法 1.数学介绍 同态加密方案基于一个难以计算的问题Ring Learning with Errorsred。这些方案中的数据在加密和未加密时都用多项式表示。 这里举一个简单的多项式…

主打熟人双向社交,UXLINK 如何用群组打造超强社交生态

社交&#xff0c;作为最强 Web3 流量入口 Web2 世界里&#xff0c;社交产品总是最具想象力。全球使用 Facebook 系列产品的日活用户&#xff08;DAP&#xff09;均值近 30 亿人&#xff0c;占全球人口的 1/3。然而&#xff0c;加密货币用户仅约有 4.2 亿&#xff0c;占全球人口…

C++ 核心编程(1)

c面向对象编程 1.内存分区模型 程序运行前为代码区和全局区。程序运行后才有栈区和堆区。。 1.1 程序运行前 #include<iostream> #include <bits/stdc.h> using namespace std; /*全局区全局变量、静态变量、常量 */ //全局变量 int g_1 20; int g_2 30; //const…

力扣刷题学习(跟随视频学着刷)

使用入门 视频链接 【手把手带你刷Leetcode力扣&#xff5c;各个击破数据结构和算法&#xff5c;大厂面试必备技能【已完结】-哔哩哔哩】 https://b23.tv/vIcRT61 时空复杂度 时间&#xff1a; 空间&#xff1a;主要有O(1)和O(n)两种&#xff0c;只用计算开辟的内存&#xff…

三种类的免费SSL证书

目前主流的三种域名证书&#xff1a;单域名证书、多域名证书、通配符泛域名证书。 这三种类型的证书根据用户域名的不同情况&#xff0c;应用场景也大不相同。 单域名证书应用场景&#xff1a; 针对于有且只有一个单独域名的单位&#xff0c;使用单域名证书是刚好能够满足需求…

DRF学习之三大认证

一、认证 1、自定义认证 在前面说的 APIView 中封装了三大认证&#xff0c;分别为认证、权限、频率。认证即登录认证&#xff0c;权限表示该用户是否有权限访问接口&#xff0c;频率表示用户指定时间内能访问接口的次数。整个请求最开始的也是认证。 &#xff08;1&#xff…

【JavaEE网络】TCP/IP协议:细节与应用

目录 TCP/IP协议协议格式传输层重点协议UDP协议UDP协议端格式 UDP的特点TCP协议TCP协议端格式 TCP的特点 TCP/IP协议 协议格式 应用层&#xff08;后端开发必知必会&#xff09;&#xff1a;这一层也有很多现成的协议&#xff08;后面还会重点介绍HTTP协议&#xff0c;这是做…

命令执行漏洞【2】vulhub远程命令执行漏洞复现

1.vulhub安装启动靶场环境 &#xff08;1&#xff09;s2-061开启靶场 &#xff08;2&#xff09;s2-059开启靶场 2.漏洞复现 &#xff08;1&#xff09;s2-061漏洞复现 github获取漏洞利用工具 开始利用 &#xff08;2&#xff09;s2-059漏洞复现 在linux特有临时目录/tmp下…

C#实现TFTP客户端

1、文件结构 2、TftpConfig.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace TftpTest {public class TftpConfig{}/// <summary>/// 模式/// </summary>public enum Modes{…

Linux论坛搭建

1.安装httpd服务 1.1安装httpd软件 [rootlocalhost yum.repos.d]# dnf install httpd 1.2.修改httpd的配置 [rootlocalhost yum.repos.d]# vim /etc/httpd/conf/httpd.conf 1.3.启动这个httpd服务,并查看它的状态 [rootlocalhost yum.repos.d]# systemctl start httpd [ro…

JavaEE——spring MVC请求处理

目录 主要目的&#xff1a; 1. Spring web 项目搭建 2. 添加依赖 3. 配置插件 4. 配置设置类 5. 编写controller层类 6. 编写测试的http请求 主要目的&#xff1a; 创建一个spring web项目&#xff1b; 创建控制类&#xff1b; 掌握如何配置MVC&#xff1b; 编写htt…

【机器学习-18】特征筛选:提升模型性能的关键步骤

一、引言 在机器学习领域&#xff0c;特征筛选是一个至关重要的预处理步骤。随着数据集的日益庞大和复杂&#xff0c;特征的数量往往也随之激增。然而&#xff0c;并非所有的特征都对模型的性能提升有所贡献&#xff0c;有些特征甚至可能是冗余的、噪声较大的或者与目标变量无关…

Django框架之python后端框架介绍

一、网络框架及MVC、MTV模型 1、网络框架 网络框架&#xff08;Web framework&#xff09;是一种软件框架&#xff0c;用于帮助开发人员构建Web应用程序和Web服务。它提供了一系列预先编写好的代码和工具&#xff0c;以简化开发过程并提高开发效率。网络框架通常包括以下功能…

常用组件(启停活动页面、活动之间传递信息、收发应用广播、操作后台服务)

启停活动页面 Activity的启动和结束 页面跳转可以使用startActivity接口&#xff0c;具体格式为startActivity(new Intent(this, 目标页面.class));。 关闭一个页面可以直接调用finish();方法即可退出页面。 Activity的生命周期 页面在安卓有个新的名字叫活动&#xff0c;因…

充电机是什么?其技术原理和行业应用

充电机是一种能够为电池充电的设备,通常由一个变压器和整流器组成。变压器将电网中的交流电转换为直流电,而整流器则将直流电转换为稳定的直流电,这种直流电可以被用来给电池充电。 充电机可以分为很多种不同类型,包括输入输出式、输入输出隔离式和车载充电机等。不同类型的充…

GoLand 2021.1.3 下载与安装

当前环境&#xff1a;Windows 8.1 x64 1 浏览器打开网站 https://www.jetbrains.com/go/download/other.html 找到 2021.1.3 版本。 2 解压 goland-2021.1.3.win.zip 到 goland-2021.1.3.win。 3 打开 bin 目录下的 goland64.exe&#xff0c;选择 Evaluate for free -- Evalu…

论文解读-面向高效生成大语言模型服务:从算法到系统综述

一、简要介绍 在快速发展的人工智能&#xff08;AI&#xff09;领域中&#xff0c;生成式大型语言模型&#xff08;llm&#xff09;站在了最前沿&#xff0c;彻底改变了论文与数据交互的方式。然而&#xff0c;部署这些模型的计算强度和内存消耗在服务效率方面带来了重大挑战&a…

ABS8-ASEMI新能源专用整流桥ABS8

编辑&#xff1a;ll ABS8-ASEMI新能源专用整流桥ABS8 型号&#xff1a;KBL410 品牌&#xff1a;ASEMI 封装&#xff1a;ABS-4 最大重复峰值反向电压&#xff1a;800V 最大正向平均整流电流(Vdss)&#xff1a;1A 功率(Pd)&#xff1a;小功率 芯片个数&#xff1a;4 引脚…