Java:优先级队列(堆)

news2024/7/6 18:29:57

一、初识【堆】

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(){
        //如果数组为空,返回-1
        if(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)   用一个集合来创建优先级队列

 实例:

 //创建一个空的优先级队列,底层默认容量为11
 PriorityQueue<Integer> q1=new PriorityQueue<>();

 //创建一个空的优先级队列,底层的容量为initialCapacity
 PriorityQueue<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.coloradmin.cn/o/1625487.html

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

相关文章

这个合租室友真的没有一点公德心,还好他搬走了

这个合租室友真的没有一点公德心&#xff0c;还好他搬走了 这个出租屋有四个房间。 有三个卧室&#xff0c;和一个隔断。 我住三个卧室中的一个。下图中右边那个就是我住的。 2023年下半年&#xff0c;左边那个屋子来了一个新租户小白。 在住的过程中&#xff0c;隔断间的租…

致力于为企业提升媒体宣传的一种新策略-软文发稿和投放

随着新媒体时代的快速发展&#xff0c;媒体宣发的方式也在不断迭代&#xff0c;其中&#xff0c;“软文发稿”成为了许多企业非常看重的一种媒体宣发方式。那么&#xff0c;什么是“软文发稿”呢&#xff1f;这是一种通过撰写有新闻属性的广告文章&#xff0c;将企业的品牌、产…

武汉星起航:凭借专业优势,助力卖家孵化,推动跨境电商发展

在全球经济一体化的大背景下&#xff0c;跨境电商行业以其独特的优势&#xff0c;成为了推动国际贸易发展的重要力量。作为这一领域的佼佼者&#xff0c;武汉星起航电子商务有限公司积极践行“走出去”战略&#xff0c;凭借自营店铺运营经验和跨境电商资源的积累&#xff0c;利…

嵌入式学习59-ARM7(自动设备号和混杂设备)

知识零碎&#xff1a; 头文件查找&#xff1a; /arm/路径下的头文件 linux驱动程序的编写&#xff0c;编译&#xff0c;运行过程 -------------------------------------------------------------------------------------------------------------------------------- 1.…

photoshop如何使用PS中的吸管工具吸取软件外部的颜色?

第一步&#xff0c;打开PS&#xff0c;随意新建一个画布&#xff0c;或打开一个图片。 第二步&#xff0c;将PS窗口缩小&#xff0c;和外部窗口叠加放置&#xff0c;以露出后面的其它页面。 第三步&#xff0c;选中吸管工具&#xff0c;在PS窗口内单击一点吸取颜色&#xff0c;…

个人信息 | Q1违规通报分析出炉,通报量环比提升180%,工信部重点方向明晰!

为协助企业了解监管部门动向&#xff0c;爱加密基于移动应用安全大数据平台长期收集个人信息违规通报信息&#xff0c;并定期抽查新上架及有更新应用的个人信息合规情况&#xff0c;未来将定期公布部分分析结果&#xff0c;以便各企业了解监管侧及市场侧情况。 根据爱加密统计…

一文带你看懂多线程编程

title: 深入理解多线程编程 date: 2024/4/25 17:32:02 updated: 2024/4/25 17:32:02 categories: 后端开发 tags: 线程同步互斥锁死锁避免竞态条件线程池异步编程性能优化 第一章&#xff1a;多线程基础 1.1 线程概念与原理 线程&#xff1a;在操作系统中&#xff0c;一个…

探索SSH:常见功能与使用技巧

文章目录 远程登录密钥认证文件传输端口转发执行远程命令会话保持总结 SSH&#xff08;Secure Shell&#xff09;是一种安全网络协议&#xff0c;用于通过加密的方式在网络上安全地进行远程登录和执行命令。它是管理远程服务器和网络设备的重要工具之一。在本文中&#xff0c;我…

【TDengine】mac m1解决no taos in java.library.path

前言 使用macos搭建springbootmybatisplus&#xff0c;通过mqtt将数据更新到tdenigne 3.2.3&#xff0c;数据源使用远程服务器的tdengine。 问题 启动时报错&#xff1a; Caused by: java.lang.UnsatisfiedLinkError: no taos in java.library.path 以下是官方文档 打开本…

【电路笔记】-Colpitts振荡器

Colpitts振荡器 文章目录 Colpitts振荡器1、概述2、基本Colpitts 振荡器电路3、示例14、使用运算放大器的Colpitts振荡器5、总结Colpitts 振荡器设计使用两个中心抽头电容器与并联电感器串联,形成产生正弦振荡的谐振储能电路。 1、概述 在许多方面,Colpitts 振荡器与我们在上…

(开源版)企业AI名片S2B2C商城系统商业计划书

团队使命 擎动人工智能跃迁&#xff0c;融技术与商业之行 项目背景 话说2022年12月7日那天&#xff0c;国务院大大发布了个重磅消息&#xff0c;宣布咱们国家的三年抗疫大战终于告一段落&#xff0c;全面放开啦&#xff01;这意味着咱们的市场经济要重新焕发生机啦&#xff…

LeetCode //C - 38. Count and Say Medium Topics Companies

38. Count and Say The count-and-say sequence is a sequence of digit strings defined by the recursive formula: countAndSay(1) “1”countAndSay(n) is the way you would “say” the digit string from countAndSay(n-1), which is then converted into a differen…

【C语言】万字详讲操作符

目录 前言 一、操作符分类 二、算数操作符 三、移位操作符 四、位操作符 五、赋值操作符 六、单目操作符 6.1 逻辑反操作 6.2 负值与正值 6.3 取地址 6.4 sizeof 6.5 取反操作符 6.6 --和操作符 6.7 间接访问操作符&#xff08;解引用操作符&#xff09; 6.8 强…

比较好的平民衣服品牌有哪些?平价质量好短袖品牌推荐

随着气候变暖&#xff0c;夏天的持续时间似乎越来越长&#xff0c;短袖作为夏季的必备服装&#xff0c;受到了广大男士的青睐。然而&#xff0c;面对市场上众多的短袖品牌和不同的质量&#xff0c;大家都觉得选短袖的时候实在难以找到质量好且合适自己的。 选择合适的短袖确实…

中级信息系统管理工程师-必会题锦集

文章目录 中级信息系统管理工程师-必会题锦集题目一CPU[解析]试题二 CPU[解析] 中级信息系统管理工程师-必会题锦集 题目一CPU CPU中&#xff08;1&#xff09;不仅要保证指令的正确执行&#xff0c;还要能够处理异常事件。 A. 运算器 B. 控制器 C. 寄存器组 D. 内部总线 [解…

Elasticsearch单机部署(Linux)

1. 准备环境 本文中Elasticsearch版本为7.12.0&#xff0c;JDK版本为1.8.0&#xff0c;Linux环境部署。 扩展&#xff1a; &#xff08;1&#xff09;查看Elasticsearch对应的常用的jdk版本如下&#xff1a;&#xff08;详情可看官网的支持一览表&#xff09; Elasticsearch a…

牛客NC221 集合的所有子集(二)【中等 深度优先,子集,排列组合 C++/Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/a3dfd4bc8ae74fad9bc65d5ced7ae813 核心 和n数之和的问题 差不多&#xff0c;都是需要找到数字的排列组合参考答案C class Solution {public:/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&a…

获取boss直聘城市地区josn数据

获取boss直聘城市地区josn数据 当我需要爬取多个城市的地区的时候&#xff0c;只能手动点击&#xff0c;然后一个一个看 结果&#xff1a; 能看到所有区域所有子地区的地区代码 解析该JSON数据 import pandas as pd import requests code[] area[] 城市代码101210100 res…

ScriptableObject数据容器讲解

概述 是Unity提供的一个用于创建可重用的数据容器或逻辑的基类。 ScriptableObject 是继承自 UnityEngine.Object 的一个类&#xff0c;但与普通的 MonoBehaviour 不同&#xff0c;它不能附加到GameObject上作为组件。 相反&#xff0c;ScriptableObject 通常用于存储和管理…

飞鹤与满趣健达成战略合作 加速深化国际化布局

继获得加拿大地区首张婴幼儿配方奶粉生产执照后&#xff0c;中国飞鹤的海外征途再添新动作。4月25日&#xff0c;中国飞鹤加拿大皇家妙克与美国婴童用品巨头满趣健&#xff08;Munchkin&#xff09;在北京正式达成战略合作。此次合作彰显了中国乳企的硬核实力&#xff0c;也是飞…