PriorityQueue底层你了解多少?(带你彻底掌握优先级队列)

1. 概念

队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。
在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)

2. 堆

JDK1.8中的PriorityQueue底层使用了堆的数据结构,而堆实际就是在完全二叉树的基础之上进行了一些元素的调整。

2.1 了解堆

1. 什么是堆?

堆(Heap)是一种特殊的完全二叉树数据结构,它满足以下两个性质之一:

  • 最大堆(Max Heap):对于任何一个非叶子节点ii的值总是不小于其子节点的值。
  • 最小堆(Min Heap):对于任何一个非叶子节点ii的值总是不大于其子节点的值。

堆通常用于实现优先级队列等数据结构。

2. 堆的性质

  • 完全二叉树:堆是一棵完全二叉树,这意味着树的每一层(除了最后一层)都完全填满,且最后一层的节点都尽可能靠左。
  • 堆序性:最大堆的每个节点的值大于或等于其子节点的值;最小堆的每个节点的值小于或等于其子节点的值。

3. 堆的表示

堆通常使用数组表示。对于一个给定的节点i

  • 左子节点的索引为2*i + 1
  • 右子节点的索引为2*i + 2
  • 父节点的索引为(i - 1) / 2

4. 堆的存储方式

因为是完全二叉树,堆(Heap)通常使用数组(或列表)来存储。这样做的主要原因是数组提供了连续的内存块,可以高效地通过索引访问元素,并且堆的性质允许我们通过数学运算轻松地找到父节点和子节点的位置。

注意:对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低。

2.2 堆的创建

对于集合{ 27,15,19,18,28,34,65,49,25,37}中的数据,如果将其创建成堆呢?

仔细观察上图后发现:根节点的左右子树已经完全满足堆的性质,因此只需将根节点向下调整好即可。

2.2.1 向下过程(以小堆为例):

 

 

public void shiftDown(int[] array, int parent) {
        // child先标记parent的左孩子,因为parent可能有左没有右
        int child = 2 * parent + 1;
        int size = array.length;
        while (child < size) {
            // 如果右孩子存在,找到左右孩子中较小的孩子,用child进行标记
            if(child+1 < size && array[child+1] < array[child]){
                child += 1;
            }
            // 如果双亲比其最小的孩子还小,说明该结构已经满足堆的特性了
            if (array[parent] <= array[child]) {
                break;
            }else{
                // 将双亲与较小的孩子交换
                int t = array[parent];
                array[parent] = array[child];
                array[child] = t;
                // parent中大的元素往下移动,可能会造成子树不满足堆的性质,因此需要继续向下调整
                parent = child;
                child = parent * 2 + 1;
            }
        }
    }

大堆为例

那对于普通的序列{1,5,3,8,7,6},即根节点的左右子树不满足堆的特性,又该如何调整呢?

public static void createHeap(int[] array) {
    // 找倒数第一个非叶子节点,从该节点位置开始往前一直到根节点,遇到一个节点,应用向下调整
    int root = ((array.length-2)>>1);
    for (; root >= 0; root--) {
        shiftDown(array, root);
    }
}

 2.3 堆的插入与删除

堆的插入总共需要两个步骤
1.先将元素放入到底层空间中(注意:空间不够时需要扩容)

2.将最后新插入的节点向上调整,直到满足堆的性质

 

public void shiftUp(int[] array,int child) {
        // 找到child的双亲
        int parent = (child - 1) / 2;
        while (child > 0) {
        // 如果双亲比孩子大,parent满足堆的性质,调整结束
            if (array[parent] > array[child]) {
                break;
            }
            else{
                // 将双亲与孩子节点进行交换
                int t = array[parent];
                array[parent] = array[child];
                array[child] = t;
                // 小的元素向上移动,成为parent之后可能它的父节点仍然不满足要求,因此需要继续向上调增
                child = parent;
                parent = (child - 1) / 2;
            }
        }
    }

堆的删除

注意:堆的删除一定删除的是堆顶元素。具体如下
1.将堆顶元素对堆中最后一个元素交换
2.将堆中有效数据个数减少一个
3. 对堆顶元素进行向下调整

2.4 用堆模拟实现优先级队列

基于前面的代码,我们很简单的就可以基于堆实现一个基本的优先级队列了。

package test;

public class PriorityQueueDemo {

    private int[] array = new int[100];
    private int size = 0;

    public void shiftDown(int[] array, int parent) {
        int child = 2 * parent + 1;
        while (child < size) {
            // 如果右孩子存在,找到左右孩子中较小的孩子,用child进行标记
            if (child + 1 < size && array[child + 1] < array[child]) {
                child++;
            }
            // 如果双亲比其最小的孩子还小,说明该结构已经满足堆的特性了
            if (array[parent] <= array[child]) {
                break;
            } else {
                // 将双亲与较小的孩子交换
                int t = array[parent];
                array[parent] = array[child];
                array[child] = t;
                // parent中大的元素往下移动,可能会造成子树不满足堆的性质,因此需要继续向下调整
                parent = child;
                child = parent * 2 + 1;
            }
        }
    }

    public void shiftUp(int[] array, int child) {
        // 找到child的双亲
        int parent = (child - 1) / 2;
        while (child > 0) {
            // 如果双亲比孩子大,parent满足堆的性质,调整结束
            if (array[parent] <= array[child]) {
                break;
            } else {
                // 将双亲与孩子节点进行交换
                int t = array[parent];
                array[parent] = array[child];
                array[child] = t;
                // 小的元素向上移动,成为parent之后可能它的父节点仍然不满足要求,因此需要继续向上调整
                child = parent;
                parent = (child - 1) / 2;
            }
        }
    }

    public void offer(int e) {
        array[size++] = e;
        shiftUp(array, size - 1);
    }

    public int poll() {
        int oldValue = array[0];
        array[0] = array[--size];
        shiftDown(array, 0);
        return oldValue;
    }

    public int peek() {
        return array[0];
    }

    public static void main(String[] args) {
        PriorityQueueDemo pq = new PriorityQueueDemo();

        // 插入测试
        pq.offer(5);
        pq.offer(3);
        pq.offer(17);
        pq.offer(10);
        pq.offer(84);
        pq.offer(19);
        pq.offer(6);
        pq.offer(22);
        pq.offer(9);

        System.out.println("插入元素后,最小元素是: " + pq.peek());

        // 提取测试
        System.out.println("提取最小元素: " + pq.poll());
        System.out.println("提取最小元素: " + pq.poll());
        System.out.println("提取最小元素: " + pq.poll());

        // 再次插入测试
        pq.offer(2);
        pq.offer(15);

        System.out.println("再次插入元素后,最小元素是: " + pq.peek());

        // 再次提取测试
        System.out.println("提取最小元素: " + pq.poll());
        System.out.println("提取最小元素: " + pq.poll());

        // 打印剩余元素
        System.out.print("优先级队列中剩余的元素: ");
        while (pq.size > 0) {
            System.out.print(pq.poll() + " ");
        }
    }
}

3. Java中的优先级队列PriorityQueue

Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,这里主要介绍PriorityQueue。

 

 3.1 构造函数

 

 注意:默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器:

// 用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
    static class IntCmp implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2-o1;
        }
    }

    public static void main(String[] args) {
        PriorityQueue<Integer> p = new PriorityQueue<>(new IntCmp());
        p.offer(4);
        p.offer(3);
        p.offer(2);
        p.offer(1);
        p.offer(5);
        System.out.println(p.peek());
    }

 3.2 插入/删除/获取优先级最高的元素

    static void TestPriorityQueue2(){
        int[] arr = {4,1,9,2,8,0,7,3,6,5};
        // 一般在创建优先级队列对象时,如果知道元素个数,建议就直接将底层容量给好
        // 否则在插入时需要不多的扩容
        // 扩容机制:开辟更大的空间,拷贝元素,这样效率会比较低
        PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
        for (int e: arr) {
            q.offer(e);
        }
        System.out.println(q.size()); // 打印优先级队列中有效元素个数
        System.out.println(q.peek()); // 获取优先级最高的元素
        // 从优先级队列中删除两个元素之和,再次获取优先级最高的元素
        q.poll();
        q.poll();
        System.out.println(q.size()); // 打印优先级队列中有效元素个数
        System.out.println(q.peek()); // 获取优先级最高的元素
        q.offer(0);
        System.out.println(q.peek()); // 获取优先级最高的元素
        // 将优先级队列中的有效元素删除掉,检测其是否为空
        q.clear();
        if(q.isEmpty()){
            System.out.println("优先级队列已经为空!!!");
        }
        else{
            System.out.println("优先级队列不为空");
        }
    }

 3.3 扩容机制

JDK1.8源码中对于PQ的扩容机制:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
        int oldCapacity = queue.length;
        // Double size if small; else grow by 50%
        int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                         (oldCapacity + 2) :
                                         (oldCapacity >> 1));
        // overflow-conscious code
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        queue = Arrays.copyOf(queue, newCapacity);
    }

private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }
优先级队列的扩容说明:
  • 如果容量小于64时,是按照oldCapacity2倍方式扩容的
  • 如果容量大于等于64,是按照oldCapacity1.5倍方式扩容的
  • 如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容

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

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

相关文章

[图解]SysML和EA建模住宅安全系统-05-参数图

1 00:00:01,140 --> 00:00:03,060 这是实数没错&#xff0c;这是分钟 2 00:00:03,750 --> 00:00:07,490 但是你在这里选&#xff0c;选不了的 3 00:00:07,500 --> 00:00:09,930 因为它这里不能够有那个 4 00:00:11,990 --> 00:00:13,850 但是我们前面这里 5 00…

D - Intersecting Intervals(abc355)

题意&#xff1a;有n个区间&#xff0c;找出俩俩区间相交的个数 分析&#xff1a; 设初始俩俩相交&#xff0c;找出不相交的&#xff08;不同区间l>r)&#xff0c;减去即可 #include<bits/stdc.h> using namespace std; typedef long long ll; int main(){ ios:…

大力出奇迹:大语言模型的崛起与挑战

随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;特别是在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;大语言模型&#xff08;LLM&#xff09;的出现与应用&#xff0c;彻底改变了我们与机器互动的方式。本文将探讨ChatGPT等大语言模型的定义、…

Hive-存储-文件格式

一、前言 数据存储是Hive的基础&#xff0c;选择合适的底层数据存储格式&#xff0c;可以在不改变Hql的前提下得到大的性能提升。类似mysql选择适合场景的存储引擎。 Hive支持的存储格式有 文本格式&#xff08;TextFile&#xff09; 二进制序列化文件 &#xff08;SequenceF…

期末复习---程序填空

注意&#xff1a; 1.数组后移 *p *(p-1) //把前一个数赋值到后一个数的位置上来覆盖后一个数 2.指针找最大字符 max *p while( *p){ if( max< *p) { max*p; qp;/ 用新的指针指向这个已经找到的最大位置&#xff1b;!!!!!!!!! } p; //因为开始没有next &#xff…

Fragment+Viewpage2+FragmentStateAdapter实现滑动式标签布局

大家好&#xff0c;我是网创有方&#xff0c;今天记录下标签布局的实现方法&#xff0c;先看下效果图。 第一步&#xff1a;编写一个activity或者fragment。内含有一个viewpager2的适配器&#xff0c;适配器类型为FragmentStateAdapter。 ​ public class MediaCreateFragment…

计算机图形学入门22:双向反射分布函数(BRDF)

1.定义 所谓BRDF&#xff08;Bidirectional Reflectance Distribution Function&#xff0c;双向反射分布函数&#xff09;&#xff0c;指的是从辐射度量学的角度去理解光线的反射&#xff0c;如下图所示。 所谓反射就是一个点从ωi方向发出的Radiance转化为dA接收到的功率E&am…

在jeesite框架中增加一个收藏夹功能-- V1.0版本

Jeesite简介&#xff1a;JeeSite 快速开发平台&#xff0c;不仅仅是一个后台开发框架&#xff0c;它是一个企业级快速开发解决方案&#xff0c;提供在线数据源管理、数据表建模、代码生成等功能。 正文&#xff1a;Jeesite是一个方便实用&#xff0c;适合敏捷开发&#xff0c;提…

openmetadata1.3.1 自定义连接器 开发教程

openmetadata自定义连接器开发教程 一、开发通用自定义连接器教程 官网教程链接&#xff1a; 1.https://docs.open-metadata.org/v1.3.x/connectors/custom-connectors 2.https://github.com/open-metadata/openmetadata-demo/tree/main/custom-connector &#xff08;一&…

Webpack: 其他性能优化

概述 前面章节我们已经详细探讨 Webpack 中如何使用分包、代码压缩提升应用执行性能。除此之外&#xff0c;还有不少普适、细碎的方法&#xff0c;能够有效降低应用体积&#xff0c;提升网络分发性能&#xff0c;包括&#xff1a; 使用动态加载&#xff0c;减少首屏资源加载量&…

Adobe Photoshop 2024 v25.5.1 中文激活版下载以及安装方法教程

软件介绍 Adobe Photoshop 2024 v25.5.1 是Adobe公司的最新版图像处理软件&#xff0c;它提供了强大的图像编辑工具和智能自动化功能&#xff0c;包括图像修复、色彩校正和滤镜效果&#xff0c;以满足专业人士和业余爱好者的需求。这款软件还支持矢量图形设计和实时协作&#…

一维信号短时傅里叶变换域邻域降噪方法(MATLAB)

噪声在人类日常生活中无处不在,其会降低语音信号的质量和可懂度。在低信噪比的恶劣环境中,这种负面影响愈发严重。为了解决这个问题,众多研究人员在过去的几十年里提出了许多降噪算法。 根据原理的不同,降噪算法可大致分为五类:谱减法、最优滤波法、基于统计模型的方法、子空间…

Java案例打印乘法口诀表,三角形

目录 一问题&#xff1a; ​编辑二代码&#xff1a; 三运行结果&#xff1a; 四问题 二代码&#xff1a; 三运行结果&#xff1a; 一问题&#xff1a; 二代码&#xff1a; package 重修;import java.util.Random; import java.util.Scanner;public class first {public …

IDEA中Java源文件编译后class文件中文乱码

文章目录 一、设置 一、设置 路径&#xff1a;File -> Settings -> Bulid, Execution,Deployment -> Compiler -> Java Compiler

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] LYA的生日聚会(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

WordPress网站如何做超级菜单(Mega Menu)?

大多数的网站菜单都是像以下这种条状的形式&#xff1a; 这种形式的是比较中规中矩的&#xff0c;大多数网站都在用的。当然还有另外一种菜单的表现形式&#xff0c;我们通常叫做“超级菜单”简称Mega Menu。网站的超级菜单&#xff08;Mega Menu&#xff09;是一种扩展的菜单&…

前端基础:CSS(篇一)

目录 css概述 CSS与HTML的关系 基本语法 行内样式表 代码 运行 内嵌样式表 代码 运行 外部样式表 代码 运行 选择器 标签选择器 代码 运行 id选择器 代码 运行 类选择器 代码 运行 选择器优先问题 通配选择器 选中所有的标签 代码 运行 选择器组…

2-22 基于matlab的NSGA-2求解多目标柔性车间调度算法

基于matlab的NSGA-2求解多目标柔性车间调度算法,计算最大完工时间、计算总延期时长、计算调度方案的总能耗、计算设备总负荷。输出四项结果&#xff0c;多次运行可寻找最佳的调度计划。程序已调通&#xff0c;可直接运行。 2-22 NSGA-2求解多目标柔性车间调度算法 - 小红书 (xi…

Java - 程序员面试笔记记录 实现 - Part2

2.1 输入输出流 流可以被看作一组有序的字节集合&#xff0c;即数据在两个设备间的传输。 字节流&#xff1a;以字节作为单位&#xff0c;读到一个字节就返回一个字节&#xff1b;InputStream & OutputStream。 字符流&#xff1a;使用字节流读到一个到多个字节先查询码…

传输距离3000M|低延迟|48K采样音频传输模块-SA356大功率发射模块

无线音频应用中&#xff0c;远距离音频传输在许多领域具有广泛的应用需求&#xff0c;例如大型会议系统、公共广播、户外活动和音乐演出等。为了满足这些需求&#xff0c;音频传输模块需要具备一些关键特性&#xff0c;包括长距离传输能力、高音质、低延迟、稳定性以及抗干扰能…