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; 动态库&#…

linux驱动开发中time_before的用法

time_before是一个在Linux内核编程中常用的宏&#xff0c;用于比较两个ktime_t类型的时间值&#xff0c;判断第一个时间值是否小于第二个时间值。 以下是time_before的一个简单示例&#xff1a; #include <linux/kernel.h> #include <linux/ktime.h>int main() {k…

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…

Reactjs数据篇

参考代码&#xff1a;GitHub - leellun/zhiqu-web-test2 1 通过createAction创建Action做数据传递 在 Redux 中定义动作的常用方法是分别声明一个动作类型常量和一个动作创建器函数来构造该类型的动作。 store/action.ts import { createAction } from "reduxjs/toolk…

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

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

三种类的免费SSL证书

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

Linux 高级网络设置

1. rp_filter 逆向路由检查 rp_filter &#xff08;Reverse Path Filtering&#xff09;参数定义了网卡对接收到的数据包进行反向路由验证的规则。他有三个值&#xff0c;0、1、2&#xff0c;具体含意如下&#xff1a; 0&#xff1a;关闭反向路由校验1&#xff1a;开启严格的…

使用脚本定时备份MySql数据库文件

如果mysql不在环境变量中&#xff0c;请先将mysql放入环境变量 #将mysql添加进环境变量中 export PATH$PATH:/usr/local/mysql/bin/#重新加载配置文件 source /etc/profile新建一个脚本 touch backup_all_databases.sh 脚本内容&#xff1a; #!/bin/bash # MySQL登录信息 …

DRF学习之三大认证

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

VUE 打包后 动态修改 后台服务器地址

使用的是第三方 continew-admin 项目 在 continew-admin-ui 项目中 添加 config.json 到public 目录下 {"baseURL": "http://localhost:8000" } 在 request.ts 文件中 async function fetchConfig() {const response await fetch(/config.json);con…

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

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

SysY 语言

SysY 语言是编译系统设计赛 要实现的编程语言。由 C 语言的一个子集扩展 而成。 每个 SysY 程序的源码存储在一个扩展名为 sy 的文件中。该文件中有且仅 有一个名为 main 的主函数定义&#xff0c;还可以包含若干全局变量声明、常量声明和其 他函数定义。 SysY 语言支持 int/…

jenkins自动化举例

使用 Jenkins 可以显著提高工作效率&#xff1a; 1. **自动化构建**&#xff1a; - 假设您是一个开发人员&#xff0c;需要频繁地编译和测试代码。手动执行这些任务可能会非常耗时。使用 Jenkins&#xff0c;您可以设置自动化构建流程&#xff0c;每当您提交新代码时&#…

RabbitMQ:消息队列的卓越之选

在当今高度互联和数据驱动的世界中&#xff0c;消息队列扮演着至关重要的角色。RabbitMQ&#xff0c;作为其中的佼佼者&#xff0c;以其高效、可靠和灵活的特性&#xff0c;赢得了众多开发者和企业的青睐。本文将深入探讨RabbitMQ的基本概念、核心特性、应用场景以及最佳实践&a…

探秘STM32MDK:编译过程与文件类型解析

探秘STM32MDK&#xff1a;编译过程与文件类型解析 在嵌入式系统开发中&#xff0c;STM32系列微控制器是广泛应用的选择之一&#xff0c;而Keil MDK&#xff08;Microcontroller Development Kit&#xff09;则是一款常用的开发工具套件。了解STM32MDK的编译过程和文件类型对于…

命令执行漏洞【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下…

多线程并发和进程通信模拟

一、实验目的&#xff1a; 通过编写多线程并发和进程通信的模拟代码&#xff0c;加深对多线程编程和进程通信的理解。学习如何使用Java中的多线程和管道流来实现并发执行和进程间通信。掌握多线程的基本概念和使用方法&#xff0c;以及进程通信的实现方式。 实验设备与实验环境…