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

news2024/12/22 23:33:31

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

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

欢迎志同道合的朋友一起加油喔🦾🦾🦾
目标梦想:进大厂,立志成为一个牛掰的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];
    }

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

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

相关文章

达梦数据库(DM)的安装教程分享

国产数据库现状 关系型数据库 Oracle 21c 银行、电力、运营商&#xff0c;9i&#xff0c;11g Sqlserver 微软&#xff0c;政府 Mysql 开源 分社区版和商业版 社区版免费 PostgreSQL 开源、国产数据二次开发、学术研究 informix Sybase ERP 行业 DB2 邮政、烟草 国内 武汉达梦(自…

前端面试大全全全全

SEO SEO是搜索引擎优化&#xff08;Search Engine Optimization&#xff09;的缩写&#xff0c;它是指通过对网站的优化来提高其在搜索引擎排名中的位置&#xff0c;从而获得更多的有机流量和更好的网站可见度。 SEO主要包括优化网站的内容、结构、代码等方面&#xff0c;使其…

ES6 块级作用域

ES6之前没有块级作用域&#xff0c;ES5的var没有块级作用域的概念&#xff0c;只有function有作用域的概念&#xff0c;ES6的let、const引入了块级作用域。 ​ ES5之前if和for都没有作用域&#xff0c;所以很多时候需要使用function的作用域&#xff0c;比如闭包。 1.1.1 什么…

基于R语言经典地理加权回归,半参数地理加权回归、多尺度地理加权回归、地理加权主成分分析、地理加权判别分析等空间异质性数据分析

目录 专题一 地理加权回归下的描述性统计学 专题二 地理加权主成分分析 专题三 地理加权回归 专题四 高级回归与回归之外 更多推荐 以地理加权回归为基础的一系列方法&#xff1a;经典地理加权回归&#xff0c;半参数地理加权回归、多尺度地理加权回归、地理加权主成分分析…

从个人角度看什么是加密算法

什么是加密&#xff1f;从程序的角度看&#xff0c;加密就是一个函数&#xff0c;它接收明文P和密钥K作为参数&#xff0c;传入加密函数运算后&#xff0c;得到的返回值&#xff0c;称之为密文C C encrypt(P, K);而解密&#xff0c;就是对加密的逆操作。把密文C和密钥K作为参…

102. 二叉树的层序遍历【206】

难度等级&#xff1a;中等 上一篇算法&#xff1a; 215. 数组中的第K个最大元素【382】 力扣此题地址&#xff1a; 102. 二叉树的层序遍历 - 力扣&#xff08;Leetcode&#xff09; 1.题目&#xff1a;102. 二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值…

构建清晰、高效的Android应用程序:了解Android架构组件

概述 Android 架构组件是一个由 Google 推出的集成库&#xff0c;旨在使 Android 应用开发更加快捷、高效和易于维护。Android 架构组件提供了一套可扩展的 API&#xff0c;帮助开发者在编写 Android 应用时&#xff0c;更好地组织应用的代码&#xff0c;并提供了一些通用的、…

C++ | 一些你所忽略的类和对象小知识

文章目录 一、再谈构造函数1、初始化列表引入初始化的概念区分语法格式及使用注意事项 2、explict关键字单参构造函数多参构造函数 二、static成员1、面试题引入2、static特性细述3、疑难解惑4、在线OJ实训5、有关static修饰变量的一些注意要点 三、匿名对象四、友元1、友元函数…

iPhone照片太多,如何快速搜索照片?

当你在iPhone中存储了大量照片&#xff0c;如何快速找到其中的某一张照片&#xff1f;通过“照片”应用&#xff0c;你可以轻松查找特定人物、地点、事物或事件的照片。 通过标签查看&#xff1a; 轻点照片应用右下角的“搜索”&#xff0c;iOS 系统会自动将照片分类显示。 时…

java944医院医疗物资管理系统

本系统以医院业务流程为基础&#xff0c;结合考虑医疗物资的特点&#xff0c;主要包含物流信息管理系统中的后勤物资仓库管理、供应消毒物资管理、科室申请领用管理以及查询决策系统等功能。 目 录 1 1课题背景 2 2整体设计 2 2.1 设计目标 2 2.2 系统架构 3…

xorm实战——结构体映射到实现数据库操作(包含导出数据库脚本)

下载引入框架 go语言中要使用orm框架需要先下载框架&#xff0c;并引入&#xff1a; // 安装工具包 go get xorm.io/xorm//安装数据库驱动&#xff08;这里是mysql&#xff09;go get -u github.com/go-sql-driver/mysql//引入框架import ("gorm.io/driver/mysql"&…

4月26日,每日互动(个推)与您相约第六届数字中国建设峰会

4月26日-30日&#xff0c;第六届数字中国建设峰会及成果展览会将在福州隆重举行。本届峰会以“加快数字中国建设&#xff0c;推进中国式现代化”为主题&#xff0c;由国家网信办、国家发改委、科技部、工信部、国务院国资委、福建省人民政府共同主办&#xff0c;福州市人民政府…

嵌入式Linux(4):应用层和内核层数据传输

文章目录 简介1、如果在应用层使用系统IO对设备节点进行打开&#xff0c;关闭&#xff0c;读写等操作会发生什么呢&#xff1f;写个例子2、假如驱动层的file_operations里面没有实现read之类的操作函数&#xff0c;会发生什么&#xff1f;3、应用层和内核层室不能直接进行数据传…

Go语言面试题--基础语法(27)

文章目录 1.下面这段代码输出什么&#xff1f;2.下面这段代码输出什么&#xff1f;3.下面这段代码输出什么&#xff1f; 1.下面这段代码输出什么&#xff1f; func main() {var a [5]int{1, 2, 3, 4, 5}var r [5]intfor i, v : range a {if i 0 {a[1] 12a[2] 13}r[i] v}f…

ROS学习第五节——话题通信之发布

首先补充一个命令ROS计算图 rosrun rqt_graph rqt_graph 1.原理讲解 话题通信实现模型是比较复杂的&#xff0c;该模型如下图所示,该模型中涉及到三个角色: ROS Master (管理者)Talker (发布者)Listener (订阅者) ROS Master 负责保管 Talker 和 Listener 注册的信息&…

数字孪生(1)

目前接触的客户群体是做大屏展示&#xff0c;闲鱼上5元包邮的那种科技感前端&#xff08;不好意思我买了&#xff09;各路模型大整合 实景GISiOT&#xff0c;如果再来点动画就好&#xff0c;然满屏动起来&#xff0c;火灾烧起来&#xff0c;水面荡漾起来&#xff0c;工程车开起…

关于GeoServer发布的wfs服务的精度问题

本周基于arcgis/core组件&#xff0c;利用arcgis api for js 4.22版本加载GeoServer发布的同一数据源的wms和wfs服务&#xff0c;出现了偏移的问题。 分析&#xff1a;同一数据源不同的访问方式&#xff0c;出现了偏移&#xff0c;这是很严重的问题。初步判断为js api加载方式的…

2023年4月北京/江苏/深圳CDGA/CDGP数据治理专家认证考试报名

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

【社区图书馆】Go语言程序设计感想

随着 Go 语言的越来越流行&#xff0c;越来越多的人对其设计和语法进行了评价。以下是一些关于 Go 技术的感想&#xff1a; Go语言的特色&#xff1a; 没有继承多态的面向对象强一致类型interface不需要显式声明(Duck Typing)没有异常处理(Error is value)基于首字母的可访问…

大学计划《数字化转型赋能教育创新发展高峰论坛》成功举办

2023年4月8日&#xff0c;由航天科技控股集团股份有限公司&#xff08;简称“航天科技”&#xff09;主办&#xff0c;CFF上海与上海电子信息职业技术学院承办、智慧树网支持的《数字化转型赋能教育创新发展高峰论坛》线上会议顺利召开。此次会议邀请到了众多教育界专家、教学名…