PriorityQueue

news2024/11/19 18:23:56

PriorityQueue其本质是一个优先级队列的集合。

1. 优先级队列

那什么是优先级队列呢?我们先从它的概念聊起。

概念:

前面介绍过队列,队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。

2. 优先级队列的模拟实现

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

问题又来了,什么叫做堆呢?

2.1 堆

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

这不是很好理解,我们来画个图:

我用绿色和灰色圈起来的我们将其看为一棵树,我们发现这颗树上的根节点的值都大于其左右子节点,这样的我们称之为大根堆;但如果是每棵树的根节点的值都小于其左右子节点,我们称之为小根堆。

堆的性质,我们上面已经提到了;

1.堆中某个节点的值总是不大于或不小于其父节点的值;

2.堆总是一棵完全二叉树。

当然啦,从堆的概念可知,堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储。

如上图,我们不会存在浪费空间的情况,但是如果是一颗非完全二叉树那么就会存在浪费空间的情况:

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

2.2 堆的创建

对于任意集合中的数据,如何将其创建成堆呢?

我们只需要象完全二叉树一样去插入,然后再对其向下(向下)调整即可。

具体实现还得看需求,需要小根堆则向下调整,需要大根堆则向上调整。

我们以向上调整为例:

首先先创建一个堆,代码如下:

public class TestHeap {
    
    public int[] elem;
    public int usedSize;//堆的有效元素数

    public TestHeap() {
        this.elem = new int[10];//初始化一个容量,后面还可以扩容
    }
}

2.3 入堆

假设有这么个堆,我们需要插入新元素98:

 从逻辑结构上看这并不是一个大根堆,我们得想办法让98到68的位置上去。此时就发生了向上调整:

一次仍然得不到大根堆,继续向上调整:

 

 再次调整后得到了一个新的大根堆。

动图演示:

向上调整

 那么思路整理好了,代码如何去写呢?

写代码前我们得知道几个概念:

将元素存储到数组中后,可以根据二叉树章节的性质对树进行还原。假设i为节点在数组中的下标,则有:
如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子
如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子

再PriorityQueue中向上调整传了一个父元素下标和一个E类型的元素,我们简单一点,只进行调整,插入元素后面的方法再写。

 堆的向上调整,调整分为如下几步

1. 假设当前插入的元素处(节点)为孩子,那么需要找到他的父亲节点,计算公式为 child = (parent - 1) / 2
2. 通过所得到的父亲与孩子(都是下标),判断二者所代表的值大小,假设当前要建大堆,如果孩子比父亲大,那么就需要交换孩子与父亲的值,孩子变成父亲,向上更新父亲;如果不满足条件,则不需要进行调整,直接结束循环即可
3. 假设这个新插入的元素(节点)很大,甚至能直接取代堆顶元素(根节点),那么循环的条件就要设为 孩子 > 0

代码如下:

 public void shiftUp(int parent) {
        int child = (parent - 1)/2 ;
        while (child > 0) {
            //找出左右孩子中最大的值与之交换
            if ( elem[child] > elem[parent]) {
                int temp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = temp;
                child = parent;
                parent = (child-1)/2;
            } else {
                break;
            }
        }
    }

 注意:

1. 在进行向上调整时,正确的找到孩子及其对应父亲是关键
无论是左孩子还是右孩子,都可以通过 parent = (child - 1) / 2 来计算父亲
2. 调整的核心是为元素找到合适的位置(这个思想很重要)
所谓合适的位置就是必须满足原则一,成为大堆或小堆

说到这里我们又不得不提一嘴堆的时间复杂度

2.4  堆的时间复杂度

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

 

故此: 建堆的时间复杂度为O(N)

写完了向上调整,我们入堆的准备工作就结束了,可以开始正式入堆。

那么我们的代码就很简单了:

思路:

1. 第一步当然是需要检查容量是否足够,不够的话需要扩容,我们写简单一点,直接用Arrays.copyOf()方法

2. 我们将需要添加进来的元素,直接向数组中放入就好

3. 插入元素后,它就不一定是一个大根堆了,那么我就需要给他向上调整以下即可

创建堆和插入代码:

public void createHeap() {
        //为什么不直接减2,而需要减1再减1?
        //因为usedSize - 1 是数组下标,在逻辑上我们从父节点到左子节点需要减1 / 2;
        for (int parent = (usedSize-1-1) / 2; parent >= 0; parent--) {
            shiftUp(parent,usedSize);
        }
}

//向上调整建堆的时间复杂度:N*logN
    public void offer(int val) {
        if (isFull()) {
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize++] = val;
        shiftUp(usedSize - 1);
    }
private boolean isFull() {
        return usedSize == elem.length;
    }

2.4 出堆

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

  • 将堆顶元素与堆底元素交换,然后将 size - -,这样就间接删除了原堆顶元素
  • 元素交换后,的整体有序性将被打破,此时需要借助向下调整函数来矫正

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

刚刚了解了啥叫向上调整那么啥叫向下调整啊?

2.4.1 向下调整 

如果父亲节点大于其左右子节点,就要将左右子节点中最小的值与父亲节点进行交换;直到没有左右子节点,或者父节点小于左右子节点。

如图所示:

动图演示:

向下调整

向下调整的步骤


1. 确认向下调整的父亲,这里是删除堆顶元素,所以父亲是0
2. 根据公式计算出目标孩子,假设左孩子为目标孩子,后续会进行判断验证
  左孩子的计算公式 leftChild = parent * 2 + 1
  右孩子的计算公式 rightChild = parent * 2 + 2
  左右孩子间隔为 1,判断验证起来也很容易
3. 判断左孩子是否为目标孩子,如果不是, child + 1 修改为右孩子,是的话就用左孩子
  如果左孩子为最后一个孩子,那么此时进行判断验证是非法的,因为会涉及到越界问题,       因此在判断验证前,需要先判断右孩子是否存在,即 child + 1 < len
4. 判断当前孩子值与父亲值间的关系,假设建大堆,如果当前孩子值大于父亲值,那么就进行值交换,父亲变成孩子,重新假设目标孩子;如果不满足条件,跳出循环即可
5. 循环结束条件为 child < len,当 child >= len时,说明此时的父亲已经是当前堆中的最小父亲了(有孩子的才叫父亲)

代码如下:

 public void shiftDown(int parent, int len) {
        int child = 2*parent + 1;
        while (child > len) {
            //存在有右孩子的情况
            if (child + 1 > len && elem[child] < elem[child + 1]) {
                child++;
            }
            //找出左右孩子中最大的值与之交换
            if ( elem[child] > elem[parent]) {
                int temp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = temp;
                parent = child;
                child = 2*parent + 1;
            } else {
                break;
            }
        }
    }

📃建堆算法

该部分稍作理解即可:

建堆算法是指直接传入一个数组,通过这个数组生成对应的大堆(小堆),建堆的步骤如下:

  1. 开辟一块足够大的空间,作为空间
  2. 拷贝数组中的所有元素至新空间内
  3. 通过两种不同的方式进行调整
    • 向上调整(效率低)
    • 向下调整(效率高)
两种调整性能比对时间复杂度数据量:100万数据量:1亿(无序)数据量:1亿(有序)
向上调整建堆F(N) = N*logN耗时29毫秒耗时3036毫秒耗时2310毫秒
向下调整建堆F(N) = N - log(N + 1)耗时22毫秒耗时2372毫秒耗时1997毫秒

推荐使用向下建堆,因为后续的堆排序Top-K用的都是向下调整

关于的其他操作:取堆顶元素、当前堆的有效元素数、判断堆是否为空等,都是很简单的功能,基本逻辑和顺序表一样,忘记的可以去看看以前的博客

3. 堆的应用

聊了这么多堆,那么我们究竟可以干什么呢?

3.1 PriorityQueue的实现

PriorityQueue是用堆作为底层结构封装优先级队列。

我们还有PriorityBlockingQueue。

简单来说PriorityQueue,这个Queue继承自AbstractQueue,是非线程安全的。

而PriorityBlockingQueue是一个BlockingQueue,所以它是线程安全的。

3.2 堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

1. 建堆
        升序:建大堆
        降序:建小堆

2. 利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

具体的堆排序在排序那一章节中会讲到。

更多关于堆的代码我放在了我的gitee上:

myHeap/src/TestHeap.java · wjm的码云/java - 码云 - 开源中国 (gitee.com)

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

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

相关文章

Grafana 中文入门教程 | 构建你的第一个仪表盘

Grafana 读音&#xff1a;/grəˈfɑːnˌɑː/ 在大厂工作久了&#xff0c;时常对一些工具的存在觉得理所当然。 比如说&#xff0c;需要计算资源的时候&#xff0c;一个配置文件就可以要来两百台虚拟化好的机子。需要试下缓存&#xff1f;点下鼠标就可以要到几十个配置好的…

简单高效的字符串匹配算法

Quick Search算法 算法简介 Quick Search算法属于Sunday算法的一种。Sunday算法由Daniel M Sunday在1990年提出。论文原文&#xff1a;A VERV FAST SU6STRINC SEARCH ALGORITHM 在论文中&#xff0c;作者提出了三个不同的算法&#xff1a;Quick Search算法、Maximal Shift算…

IO流——字符缓冲流

复制文本文件–一次读写一个字符 –一次读写一个字符数组 不带参数&#xff1a; package com.demo03;import java.io.FileReader; import java.io.FileWriter; import java.io.IOException;/** 需求&#xff1a;* 把项目路径下的FileWriterDemo.java中的内容复制到项目路径…

上门服务小程序怎么开发-上门服务小程序源码功能

目前上门服务类型新型行业已经占据了很大的市场所在&#xff0c;上门家政服务&#xff0c;上门做饭&#xff0c;上门按摩&#xff0c;上门私教&#xff0c;上门美容没发等等一些列的&#xff0c;目前上门系列的在市场分额还是特别大的&#xff0c;趋势比较大&#xff0c;今天就…

Docker 安装镜像与使用命令

按照阿里云的操作文档安装 docker&#xff1a;https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors 这里仅写 Windows 版的&#xff0c;其他版本见上面的链接 1. 安装&#xff0f;升级Docker客户端 对于Windows 10以下的用户&#xff0c;推荐使用Docker ToolboxWin…

一文带你了解UI自动化测试框架

PythonSeleniumUnittestDdtHTMLReport分布式数据驱动自动化测试框架结构 1、Business&#xff1a;公共业务模块&#xff0c;如登录模块&#xff0c;可以把登录模块进行封装供调用 ------login_business.py from Page_Object.Common_Page.login_page import Login_Page from H…

九龙证券|首月定增市场“开门红” 上市公司密集融资谋扩产

2023年首月&#xff0c;A股定增市场迎来“开门红”。 据上海证券报记者统计&#xff0c;年初至今&#xff0c;已有35家公司完成定增&#xff0c;合计募资986.86亿元&#xff0c;较上年同期的477.3亿元大幅增长超100%&#xff0c;延续了2022年末的高位运行态势。同时&#xff0c…

前端项目发布后,如何使正在使用的用户更新为最新的版本?

1.背景 每次项目上线后&#xff0c;异常监控总是零零散散报一些资源加载或者解析失败的告警 仔细对比chunk的hash值会发现已经是上一版本的js文件为什么会出现这个问题呢&#xff1f;也不难想到&#xff0c;项目是单页应用&#xff0c;页面使用懒加载分多个chunk打包&#xff…

SDP零信任网络安全架构

安全狗零信任SDP接入解决方案基于“以身份认证为中心&#xff0c;以信任为基础&#xff0c;持续动态授权认证”的理念&#xff0c;打造企业全方位立体业务访问安全体系。 其SDP零信任网络安全架构如下图&#xff1a; SDP零信任产品优势 1、多维度终端环境感知 系统风险感知&…

浅谈--声调

声调 根据声调的有无&#xff0c;我们可以把世界上的语言分为声调语言和非声调语言两大类。 非声调语言&#xff0c;并不是说音节没有高低升降的音高变化&#xff0c;只是这种变化只能改变语气作用&#xff0c;并不能区别意义。如&#xff1a;在英语单词book&#xff0c;音高…

Nacos 初始

1.Nacos 的安装使用。 nacos的安装步骤 1.端口配置 Nacos的默认端口是8848&#xff0c;如果你电脑上的其它进程占用了8848端口&#xff0c;请先尝试关闭该进程。 如果无法关闭占用8848端口的进程&#xff0c;也可以进入nacos的conf目录&#xff0c;修改配置文件中的端口&am…

(考研湖科大教书匠计算机网络)第三章数据链路层-第五节:点对点协议PPP

专栏目录首页&#xff1a;【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一&#xff1a;点对点协议&#xff08;PPP&#xff09;概述二&#xff1a;PPP协议组成部分三&#xff1a;PPP协议帧格式四&#xff1a;PPP协议的透明传输&#xff08;1&#xff09;字节填充法…

磨金石教育摄影技能干货分享|优秀摄影作品如何表现创意与思想的

在摄影活动中并不是简单的利用镜头把事物拍下来&#xff0c;而是要尽量的融入作者对当时当地事物的情意表达出来。利用摄影技术&#xff0c;因时制宜展现个人的创意&#xff0c;然后表达自己的思想。下面给大家分享一组摄影大赛获奖作品&#xff0c;看看这些获奖作品如何做到的…

vue(iviewui) 输入框历史记录

安装&#xff1a;npm install good-storage -S 缓存cache.js: /*把搜索的结果保存下来*/ /*用export把方法暴露出来*/ /*定义存储搜索的key _search_定义内部使用的key*/ let searches_list [] const SEARCH_MAX_LENGTH15 /*插入方法 arr存储的数据 val传入存储的值 comp…

leetcode-每日一题-1664-生成平衡数组的方案数(中等,动态规划)

时间长不做动态规划的题目&#xff0c;现在突然看过去有些生疏&#xff0c;第一眼看到这个题目想了一下暴力&#xff0c;然后突然注意到了题目的难度是中等&#xff0c;力扣里面的中等难度的题目都是没有暴力可以做出来的&#xff0c;目前我做这么多题来看的话&#xff0c;第一…

StarRocks荣获开源中国“2022 年度优秀开源技术团队”

近日&#xff0c;国内知名开源技术社区开源中国&#xff08;OSCHINA&#xff09;&#xff0c;综合了平台上各大认证官方技术团队、开源社区账号年度发表的内容频率及质量、开展各种活动运营积极性等多方面表现&#xff0c;向StarRocks颁发了 OSCHINA“2022年度优秀开源技术团队…

编译原理学习笔记19——语义分析和中间代码生成4

编译原理学习笔记19——语义分析和中间代码生成419.1 常用的控制语句19.2 控制语句的属性文法19.3 控制语句的属性计算19.4 一遍扫描翻译控制语句19.5 一遍扫描翻译控制语句示例19.1 常用的控制语句 常用的控制语句 S → if E then S1S → if E then S1 else S2S → while E …

机器学习笔记之生成对抗网络(二)全局最优解的求解逻辑

机器学习笔记之生成对抗网络——全局最优解的求解逻辑引言回顾&#xff1a;生成对抗网络的判别过程关于生成对抗网络的一些特性最优解的求解过程引言 上一节介绍了生成对抗网络&#xff0c;并介绍了其判别过程&#xff0c;本节将介绍关于模型参数的求解逻辑。 回顾&#xff1…

建立建筑领域科学的碳排放核算方法(江亿)

中国工程院院士、清华大学江亿教授受邀参加2022年12月28日“2022中国建筑节能协会年会暨第五届全国建筑节能与绿色建筑技术创新大会”并作了题为“建立建筑领域科学的碳排放核算方法”的报告。 实现能源转型&#xff0c;由碳基能源转为零碳能源&#xff0c;是中央确定的未来战…

九龙证券|锂离子电池在安全性能上应当满足哪些条件?

锂离子电池具有分量轻、容量大、无回忆效应等优势&#xff0c;因而得到了普遍运用——今朝的许多数码设备都采用了锂离子电池作电源&#xff0c;虽然其价格相对来说比较昂贵。锂离子电池的能量密度很高&#xff0c;它的容量是同分量的镍氢电池的1.5~2倍&#xff0c;而且具有很低…