【Java 数据结构】优先级队列 (堆)

🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!

人生格言:当你的才华撑不起你的野心的时候,你就应该静下心来学习!

欢迎志同道合的朋友一起加油喔🦾🦾🦾
目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个🐒嘿嘿
谢谢你这么帅气美丽还给我点赞!比个心


目录

1.优先级队列和堆的概念

2. 向下调整算法

3.向上调整算法

4.向上调整算法和向下调整算法的建堆时间复杂度

1.向上调整算法建堆:

2.向下调整算法建堆:

5.优先级队列的实现

 5.2插入数据

 5.3删除数据

5.4获取堆顶元素



1.优先级队列和堆的概念

1.1.什么是优先级队列?

我们都学过队列,队列是一种先进先出的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,这就是优先级队列。比如有时候我们在打游戏的时候,别人打电话给你,那么系统一定是先处理打进来的电话。

1.2.什么是堆?

堆是一个(特殊的)完全二叉树,每个父节点都不大于或者不小于自己的孩子节点,层序遍历这个二叉树,顺序的放入一个数组中,这就是堆的存储。从逻辑上来说,堆是一棵完全二叉树,从存储底层来说,堆底层是一个数组。按种类分,它又分为大根堆和小根堆,请看下图:

将元素存储到数组后,在以实现树的方式对堆进行实现。
设 i 结点为数组中的下标,则有以下特点:

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

注意堆是一棵完全二叉树,它有着层序遍历的规则,所以采用顺序存储的方式来提高效率而非完全二叉树是不适合使用顺序存储的方式来进行存储的,因为它为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低。


2. 向下调整算法

向下调整算法(Heapify Down)是一种用于维护堆(最大堆或最小堆)性质的算法。当堆中的某个节点的值不满足堆性质(在最大堆中,每个节点的值都应大于或等于其子节点的值;在最小堆中,每个节点的值都应小于或等于其子节点的值)时,向下调整算法会将该节点向下移动,直到它满足堆性质。

向下调整算法通常用于以下场景:

  1. 当从堆中删除最大值(最大堆)或最小值(最小堆)时,通常将堆的最后一个元素移动到堆顶,然后进行向下调整,以重新满足堆的性质。
  2. 当初始化一个堆时,可以自底向上地对所有非叶子节点进行向下调整,以构建一个有效的堆结构。

向下调整算法的过程如下:

  1. 从不满足堆性质的节点开始,找到其子节点中的最大值(最大堆)或最小值(最小堆)。
  2. 如果当前节点的值小于(最大堆)或大于(最小堆)子节点中的最大值或最小值,则将当前节点与该子节点交换。
  3. 重复以上过程,直到当前节点满足堆的性质,或已经到达堆的底部。

图示:

 这种算法通过不断地将不满足堆性质的节点向下移动,直到找到合适的位置,从而维护了堆的性质。注意,向下调整算法在最坏情况下的时间复杂度为O(logn),其中n是堆中元素的数量。

3.向上调整算法

向上调整算法(Heapify Up)是一种用于维护堆(最大堆或最小堆)性质的算法。当堆中的某个节点的值不满足堆性质(在最大堆中,每个节点的值都应大于或等于其子节点的值;在最小堆中,每个节点的值都应小于或等于其子节点的值)时,向上调整算法会将该节点向上移动,直到它满足堆性质。

向上调整算法通常用于以下场景:

  1. 当向堆中插入一个新元素时,通常将该元素添加到堆的末尾,然后进行向上调整,以重新满足堆的性质。

向上调整算法的过程如下:

  1. 从不满足堆性质的节点开始,找到其父节点。
  2. 如果当前节点的值大于(最大堆)或小于(最小堆)其父节点的值,则将当前节点与其父节点交换。
  3. 重复以上过程,直到当前节点满足堆的性质,或已经到达堆的顶部。

这种算法通过不断地将不满足堆性质的节点向上移动,直到找到合适的位置,从而维护了堆的性质。注意,向上调整算法在最坏情况下的时间复杂度为O(logn),其中n是堆中元素的数量。

向上调整算法(Heapify Up)和向下调整算法(Heapify Down)都可以用于建堆,它们的时间复杂度有所不同。

4.向上调整算法和向下调整算法的建堆时间复杂度

1.向上调整算法建堆:

向上调整算法建堆的过程是从一个空堆开始,依次将数组中的元素插入堆中。对于每个插入的元素,执行向上调整。在这个过程中,每个元素的向上调整最多需要O(logn)时间(其中n是堆中元素的数量),因为堆的高度是logn。

为什么向下调整算法建堆的时间复杂度为O(logn)?

由二叉树的性质可以得知,每层结点的个数是2^(h-1),在第几层的结点如果需要调整的话,最多需要向上调整h-1次。那么,调整次数将与高度相关,如下

  因此,向上调整算法建堆的总时间复杂度为O(nlogn)。这是因为我们需要对n个元素执行向上调整操作,每个操作的时间复杂度为O(logn)。

2.向下调整算法建堆:

向下调整算法建堆的过程是自底向上地对所有非叶子节点进行向下调整。从最后一个非叶子节点开始,依次向前遍历,对每个节点进行向下调整。这种方法的时间复杂度为O(n)。

为什么向下调整算法建堆的时间复杂度为O(n)?

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果)

 综上所述,向上调整算法建堆的时间复杂度为O(nlogn),而向下调整算法建堆的时间复杂度为O(n)。在实际应用中,向下调整算法建堆通常比向上调整算法建堆更高效。


5.优先级队列的实现

5.1创建大根堆 (向下调整建堆)

public class TestHeap {
    public int elem[];
    public int usedSize;
    public static int DEFAULT_SIZE = 10;
 
    public TestHeap() {
        this.elem = new int[DEFAULT_SIZE];
    }
 
    public int[] createHeap(int[] array) {
        //准备数据
        for (int i = 0; i < array.length; i++) {
            this.elem[i] = array[i];
            this.usedSize++;
        }
        //创建大根堆
        for (int p = (this.usedSize - 1 - 1) / 2; p >= 0 ; p--) {
            //向下调整
            shiftDown(p, this.usedSize);
        }
        return this.elem;
    }
    private void shiftDown(int parent, int len) {
        //左孩子
        int child = 2 * parent + 1;
        //每一次调整的结束条件 --> child < len
        while(child < len) {
            //child < len 保证了有右孩子再去比较,拿到左右孩子的最大值
            if(child + 1 < len && this.elem[child] < this.elem[child + 1]) {
                child++;
            }
            //如果孩子节点大于父节点就交换
            if(this.elem[child] > this.elem[parent]){
                swap(parent, child);
                //继续判断它子树是否调整
                parent = child;
                child = 2 * parent + 1;
            } else {
                //无需调整就退出循环
                break;
            }
        }
    }
    private void swap(int parent, int child) {
        int tmp = this.elem[parent];
        this.elem[parent] = this.elem[child];
        this.elem[child] = tmp;
    }
}
  • 思路

 5.2插入数据

   public void offerHeap(int val) {
        if(isFull()) {
            this.elem = Arrays.copyOf(this.elem, 2*this.elem.length);
        }
        //1.放在最后一个位置
        this.elem[this.usedSize] = val;
        //2.进行向上调整
        shiftUp(usedSize);
        //3.有效数据+1
        this.usedSize++;
    }
    private void shiftUp(int child) {
        //父节点
        int parent = (child - 1) / 2;
        while(child > 0) {
            //如果新插入元素比父节点大就交换
            if(this.elem[child] > this.elem[parent]) {
                swap(child,parent);
                //向上调整
                child = parent;
                parent = (child - 1) / 2;
            } else {
                break;
            }
        }
    } 
    private boolean isFull() {
        return this.usedSize == this.elem.length;
    }
  • 思路

1.检查容量是否足够,不够的话需要扩容

2.将待插入的数据放在 usedSize 位置

3.从usedSize下标的数据向上调整

向上调整需要一个前提,就是当前的堆需要是大根堆(小根堆),我们以大根堆举例:

 假如99这个结点是新插入的,需要将该树的结构重新改为大根堆,那么需要进行向上调整,具体过程就是:依次从下往上跟它的父节点比较,如果大于父节点则交换

可能会有人问不需要与左右孩子结点比较吗?

答案:不需要,因为在插入之前,该树就是一个大根堆,它的所有孩子节点都小于父节点,我们只需要修复新元素与其父节点之间的关系即可.

 5.3删除数据

    public int pollHeap() {
        if(isEmpty()) {
            throw new MyHeapIsEmptyException("优先级队列为空!");
        }
        int tmp = this.elem[0];
        //将最后一个数据与堆顶数据进行交换
        swap(usedSize-1,0);
        this.usedSize--;
        //向下调整
        shiftDown(0, this.usedSize);
        return tmp;
    }
    private boolean isEmpty() {
        return this.usedSize == 0;
    }
  • 思路

出堆,出的是堆顶元素,即下标为0处的元素,因为对于数组来说,头删是十分不利的,因此我们这里学要借助一个小技巧:

1.将最后一个数据与堆顶数据进行交换

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

2.然后再进行向下调整

5.4获取堆顶元素

    public int peekHeap() {
        if(isEmpty()) {
            throw new MyHeapIsEmptyException("优先级队列为空!");
        }
        return this.elem[0];
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

快速精简软件,如何让软件缩小到原来的5%大小,从删除文件入手,到修改C++引用库,合规解决存储问题

Hi~大家好&#xff0c;今天制作一个简单的精简软件的教学~ 事先说明下&#xff0c;精简软件并不违反任何规定&#xff0c;尤其是开源软件&#xff0c;这里也仅讨论开源软件的修改&#xff0c;根据几乎所有开源软件的开源规则&#xff0c;精简软件&#xff0c;本质也就是修改软件…

戴尔G3 Ubuntu18.04双系统安装

ROS学习需要使用Linux系统&#xff0c;首先就是Ubuntu&#xff0c;我选择的是18.04.6这个版本&#xff0c;因为后面我要使用以Jetson Nano为主控的Jetbot进行ROS编程&#xff0c;Jetbot所带的出厂镜像就是18.04&#xff0c;为了方便程序移植&#xff0c;以及减少不必要的麻烦。…

【消息队列】聊一下Kafka副本机制

副本机制的好处 副本在分布式系统下&#xff0c;不同的网络互联的机器保存同一份数据。我们知道在分布式系统中&#xff0c;都会通过数据镜像、数据冗余的方式来提升高可用性。 提供数据冗余&#xff1a;这点比较好理解&#xff0c;说白了就是通过数据冗余在不同的服务器上&a…

大家副业都在做什么?csgo搬砖靠谱的副业推荐给你

从来没想过&#xff0c;以前只会玩CSGO的男孩子&#xff0c;现在居然能借助游戏赚到钱了&#xff01;甚至不需要什么专业的技巧&#xff0c;简简单单 在steam平台选择有利润的道具后&#xff0c;再上架到国内网易BUFF平台&#xff0c;赚取“信息差”差价而已&#xff01; 谁大…

SpringCloud学习(六)——Feign的简单使用

文章目录 1. Feign 的使用1.1 引入依赖1.2 添加注解1.3 编写Feign客户端1.4 测试 2. Feign中的自定义配置2.1.配置文件方式2.2.Java代码方式 3. Feign 性能优化4. Feign的抽取式使用4.1 抽取配置4.2 引入依赖4.3 指明Client 在此之前&#xff0c;我们服务之间需要进行调用的时候…

读懂MAC地址

MAC地址是一种用于标识计算机网络设备的唯一地址。它是由48个二进制数字组成的&#xff0c;通常表示为12个十六进制数字&#xff0c;每两个数字之间用冒号或连字符分隔开。MAC地址由设备制造商在生产过程中分配&#xff0c;以确保网络上每个设备都有唯一的标识符。 MAC地址的规…

第11章_常用类和基础API

第11章_常用类和基础API 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 本章专题与脉络 1. 字符串相关类之不可变字符序列&#xff1a;String 1.1 String的特性 java.lang.String 类代表字符串…

【大数据之Hadoop】十七、MapReduce之数据清洗ETL

ETL是将业务系统的数据经过抽取、清洗转换之后加载到数据仓库的过程&#xff0c;目的是将分散、零乱、标准不统一的数据整合到一起&#xff0c;为决策提供分析依据。 ETL的设计分三部分&#xff1a;数据抽取、数据的清洗转换、数据的加载。 1 ETL体系结构 ETL主要是用来实现…

LLM总结(持续更新中)

引言 当前LLM模型火出天际&#xff0c;但是做事还是需要脚踏实地。此文只是日常学习LLM&#xff0c;顺手整理所得。本篇博文更多侧重对话、问答类LLM上&#xff0c;其他方向&#xff08;代码生成&#xff09;这里暂不涉及&#xff0c;可以去看综述来了解。 之前LLM模型梳理 …

龙芯中科官方宣布,龙芯中科企业办公信息化平台全面完成国产化替代

4月4日&#xff0c;龙芯中科官方宣布&#xff0c;龙芯中科企业办公信息化平台全面完成国产化替代。龙芯 ERP 系统全系统使用国产化平台&#xff0c;私有化部署于基于龙芯 3C5000 服务器集群的虚拟化云平台上&#xff0c;使用自研 Loongnix 操作系统、自研 LoongDB 数据库及龙芯…

【SQL Server】无需公网IP,就可以远程连接SQL Server数据库

目录 1.前言 2.本地安装和设置SQL Server 2.1 SQL Server下载 2.2 SQL Server本地连接测试 2.3 Cpolar内网穿透的下载和安装 2.3 Cpolar内网穿透的注册 3.本地网页发布 3.1 Cpolar云端设置 3.2 Cpolar本地设置 4.公网访问测试 5.结语 1.前言 数据库的重要性相信大家…

Redis-----什么是Redis?

什么是Redis&#xff1f; redis是一个基于内存的key-value结构数据库。 基于内存存储&#xff0c;读写性能高适合存储热点数据&#xff08;热点商品、资讯、新闻&#xff09;企业应用广泛 Redis入门 redis简介 redis是一个开源的内存中的数据结构存储系统&#xff0c;数据库…

哪个洗脱一体机好用?好用的洗拖一体机推荐

洗地机是一款使用非常方便的清洁工具&#xff0c;通常可以实现吸、拖、洗三个功能&#xff0c;对于各类家庭污渍都有着不错的处理能力&#xff0c;无论是干燥垃圾还是潮湿垃圾一律可以有效清理。不过很多新手朋友在选购洗地机时会因为看不懂参数而频繁踩雷。本文为大家整理了洗…

详解语义分割deeplabv3+模型的工业应用流程

来源&#xff1a;投稿 作者&#xff1a;某一个名字 编辑&#xff1a;学姐 导语 在工业视觉应用中&#xff0c;目标检测算法常用于特征的粗定位&#xff0c;而语义分割则在特征的精定位方面有着突出的表现。使用较多的语义分割模型主要有FCN、deeplab系列、unet等&#xff0c;根…

keil5使用c++编写stm32控制程序

keil5使用c编写stm32控制程序 一、前言二、配置图解三、std::cout串口重定向四、串口中断服务函数五、结尾废话 一、前言 想着搞个新奇的玩意玩一玩来着&#xff0c;想用c编写代码来控制stm32&#xff0c;结果在keil5中&#xff0c;把踩给我踩闷了&#xff0c;这里简单记录一下…

【OCR】CTC loss原理

1 CTC loss出现的背景 在图像文本识别、语言识别的应用中&#xff0c;所面临的一个问题是神经网络输出与ground truth的长度不一致&#xff0c;这样一来&#xff0c;loss就会很难计算&#xff0c;举个例子来讲&#xff0c;如果网络的输出是”-sst-aa-tt-e’, 而其ground truth…

深入剖析:如何优化Android应用的性能和内存管理

深入剖析&#xff1a;如何优化Android应用的性能和内存管理 性能和内存管理的重要性 在今天的移动应用开发中&#xff0c;用户对于应用的性能和体验要求越来越高。一款性能卓越的Android应用能够提供流畅的操作体验、快速的响应速度以及较低的资源消耗&#xff0c;从而提高用户…

SpringBoot 集成webSocket

pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 …

HTML5 Input 类型

文章目录 HTML5 Input 类型Input 类型: colorInput 类型: dateInput 类型: datetimeInput 类型: datetime-localInput 类型: emailInput 类型: monthInput 类型: numberInput 类型: rangeInput 类型: searchInput 类型: telInput 类型: timeInput 类型: urlInput 类型: weekHTM…

CLIMS:弱监督语义分割的跨语言图像匹配

文章目录 CLIMS: Cross Language Image Matching for Weakly Supervised Semantic Segmentation摘要方法语言图像匹配框架 实验结果 CLIMS: Cross Language Image Matching for Weakly Supervised Semantic Segmentation 摘要 存在的问题 CAM(类激活图)通常只激活有区别的对象…