源码角度详解Java中的优先队列PriorityQueue(堆的实现)

news2025/3/12 10:17:55

if (e == null)

throw new NullPointerException();

modCount++;

int i = size;

if (i >= queue.length)

grow(i + 1);

size = i + 1;

if (i == 0)

queue[0] = e;

else

siftUp(i, e);

return true;

}

在offer方法中,我们会先判断数组是否需要扩容,然后判断当前队列是否是空队列,如果是空,插入的元素直接作为数组的第一个元素,也就是完全二叉树的根节点存在。如果不是就执行siftUp(i, e);方法,下面我们来看看siftUp方法的实现:

private void siftUp(int k, E x) {

if (comparator != null)

siftUpUsingComparator(k, x);

else

siftUpComparable(k, x);

}

如果我们传入了一个构造器,我们就调用siftUpUsingComparator(k, x),否则调用 siftUpComparable(k, x)方法,这两个方法其实大同小异(只是比较的方式不一样),我们这里就选siftUpComparable来看看:

private void siftUpComparable(int k, E x) {

Comparable<? super E> key = (Comparable<? super E>) x;

while (k > 0) {

int parent = (k - 1) >>> 1;

Object e = queue[parent];

if (key.compareTo((E) e) >= 0)

break;

queue[k] = e;

k = parent;

}

queue[k] = key;

}

当我们的k(作为第几个元素插入)大于0,我们拿当前插入的元素和数组中的第(k-1)/2个元素进行比较,如果大于,直接跳出循环,直接将数组第k个元素复制为x,即我们的插入元素。如果小于,我们将数组上原来(n-1)/2位置上的元素存放在k位置上,然后继续第(k-1)/2个数,做比较,直到我们的k等于0或者我们插入的元素大于(k-1)/2位置上的元素时,跳出循环。

为什么要和我们的(k-1)/2个元素去比较呢?

上面我们说过,我们的优先队列其实是个堆(一个棵完全的二叉树)。下面我们看张图大家就懂了:

在这里插入图片描述

如上图所示,我们要在原有的小根堆中插入一个数为11,我们最原始的大根堆转为数组表示为:[9,17,65,23,45,78,87,53]。(数组中的元素个数从0开始)

现在我们要插入一个数11,是数组中的第8个元素。因为我们的堆是个完全二叉树,所以我们从最后一层开始找到最近的空的叶子节点(即23的右子节点),在插入一个数后,我们现有的堆,不满足我们小根堆的性质了,所以我们需要重新调整,怎么调整呢,我们和它的父节点23开始比较,23大于11,交换两个数的位置,还是不满足,我们继续往上比较,拿17和11比较还是大,我们继续往上找到9,9大于我们的小于,所以我们的11放在原来17的位置。细心的朋友肯定已经发现了,第(k-1)/2个元素其实就是我们的11在插入和移动过程中的父节点。(8-1)/2等于3,数组中的第3个元素就是23即我们一开始的插入位置的父节点,(3-1)/2等于1,即数组中的第1个元素17。

我们的offer方法其实就是上述代码的实现,每次offer方法执行结束,我们的数组还是满足堆的性质,优先级最大的始终在数组的第一个元素,保证我们优先每次取出的元素,都是优先级最大的(即数值最小的)。

三.poll方法()


public E poll() {

if (size == 0)

return null;

int s = --size;

modCount++;

E result = (E) queue[0];

E x = (E) queue[s];

queue[s] = null;

if (s != 0)

siftDown(0, x);

return result;

}

出队,取出数组中的第0个元素。用变量x记录数组中的最后一个数,再将最后一个数置为null(移除最后一个元素)。然后执行siftDown方法,下面我们看看siftDown方法的实现:

private void siftDown(int k, E x) {

if (comparator != null)

siftDownUsingComparator(k, x);

else

siftDownComparable(k, x);

}

private void siftDownComparable(int k, E x) {

Comparable<? super E> key = (Comparable<? super E>)x;

int half = size >>> 1; // loop while a non-leaf

while (k < half) {

int child = (k << 1) + 1; // assume left child is least

Object c = queue[child];

int right = child + 1;

if (right < size &&

((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)

c = queue[child = right];

if (key.compareTo((E) c) <= 0)

break;

queue[k] = c;

k = child;

}

queue[k] = key;

}

我们直接分析siftDownComparable方法,和siftUpComparable相反,我们siftDownComparable是从上到下进行的,每次都和数组中第2k+1和2k+2的中较小的数比较,如果比较小的数大,我们就和较小的数交换位置否则我们的数就放在k位置上,k从0开始。

我们来用个小根堆分析下:

在这里插入图片描述

如图所示:我们现在要删除第一个元素9,然后我们取出最后一个数23,拿他和11和65中较小的数11比较,发现23比11大,将11放置在数组的第一个位置,即树的根节点,然后拿23跟17,45中较小的数字17比较,23>17,17 上移,然后拿23和53比较,小于.,所以23占据原来17的位置。

最后结果即:

在这里插入图片描述

最小值11跑到了根节点(即优先级最高),整个完全二叉树,还满足堆的性质。

四.remove(Object obj)方法


下面我们来看看PriorityQueue的remove方法。

public boolean remove(Object o) {

int i = indexOf(o);

if (i == -1)

return false;

else {

removeAt(i);

return true;

}

}

private int indexOf(Object o) {

if (o != null) {

for (int i = 0; i < size; i++)

if (o.equals(queue[i]))

return i;

}

return -1;

}

E removeAt(int i) {

// assert i >= 0 && i < size;

modCount++;

int s = --size;

if (s == i) // removed last element

queue[i] = null;

else {

E moved = (E) queue[s];

queue[s] = null;

siftDown(i, moved);

if (queue[i] == moved) {

siftUp(

i, moved);

if (queue[i] != moved)

return moved;

}

}

return null;

}

首先我们通过遍历当前数组找到要删除元素的索引,然后调用removeAt方法。如果删除的元素,是堆的根节点,则队列为置为空,否则我们找到当前堆的最后一个叶子节点,即数组的最后一个数组,从i(删除的位置开始)执行siftDown操作。如果执行完siftDown操作,删除的位置上的值等于原本数组的最后一位的值在执行从i位置开始shiftUp操作。

还拿上面堆来说明:

在这里插入图片描述

如果我们现在要移除元素11,我们取出最后一个节点元素23从11位置开始从下比较,左后结果就是23取代了17的位置,17取代了11的位置结果如下:

在这里插入图片描述

五.优点

相比普通数组,删除和插入的复杂度O(n),PriorityQueue插入和删除元素的复杂度都是O(logn),不需要频繁的移动数组元素,效率比较高。每次删除和插入元素,都会自动调整位置,保证优先级最高的始终在当前数组的第一个。

g?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JyYWludHQ=,size_16,color_FFFFFF,t_70)

五.优点

相比普通数组,删除和插入的复杂度O(n),PriorityQueue插入和删除元素的复杂度都是O(logn),不需要频繁的移动数组元素,效率比较高。每次删除和插入元素,都会自动调整位置,保证优先级最高的始终在当前数组的第一个。

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

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

相关文章

DataX 原理解析和性能优化

datax简介 datax是阿里开源的用于异构数据源之间的同步工具&#xff0c;由于其精巧的设计和抽象&#xff0c;数据同步效率极高&#xff0c;在很多公司数据部门都有广泛的使用。本司基于datax在阿里云普通版的rds服务器上实现了通过公网&#xff0c;从阿里云杭州到美国西部俄勒…

一种基于Spark深度随机森林的网络入侵检测模型

一种基于Spark深度随机森林的网络入侵检测模型学习目标学习内容目前存在的不足为了解决这个问题特征分片深度并行随机森林Deep Parallel Random Forest(DPRF)投票策略Spark 上的并行化高复用缓存计算每个RDD的权重分层替换模型评估局限性参考论文申明&#xff1a; 未经许可&…

ASP.NET Core Web API 学习笔记

目录 一、Demo 1. 创建项目 2. 启动项目 3. 编写 api demo 二、C# .NET WEB 程序结构 一、Demo 1. 创建项目 创建的项目结构如下: Properties 配置文件&#xff0c;存放了一些 .json 文件用于配置 ASP.NET Core 项目 Propertics/launchSettings.json 启动配置文件&…

以岭药业:连花清瘟火爆背后,数字化重塑人力资源管理

近日&#xff0c;随着疫情防控政策“国十条”发布&#xff0c;新冠防疫政策逐步进入后防疫时代&#xff0c;每个人要做自己健康的“第一责任人”。而连花清瘟作为中医药治疗新冠肺炎筛选出的“三药三方”之一&#xff0c;也成为新疫情防控模式下的家庭常备药。连花清瘟自上市以…

怎么将图片内容转换成文字?这两种方法可以轻松实现

如何将图片的内容转换成文字呢&#xff1f;大家在使用图片文件的时候&#xff0c;遇到那种图片中包含一些有用的文字信息时&#xff0c;没有办法直接复制下来使用&#xff0c;只能对照着图片将文字信息给记录下来&#xff0c;这样会很耗费我们的时间。其实是有方法能够直接将图…

Python学习中常见的几个报错,看你踩雷没

前言 嗨嗨 今天给大家统计一下, 在学习Python中遇到的常见报错, 不一定会很全面, 但是应该会持续更新, 有用的话, 记得收藏哦~ 下面我会总结遇到的报错截图, 给出解决办法, 如果有需要补充的报错, 或者你解决不了的报错, 欢迎来文章最下方QQ群里面找我~ 1. 模块未安装 (Mod…

面试中这样介绍自己的项目经验,轻松拿Offer

面试时7分靠能力&#xff0c;3分靠技能&#xff0c;而刚开始时的介绍项目又是技能中的重中之重&#xff0c;所以本文将从“介绍”和“引导”两大层面告诉大家如何准备面试时的项目介绍。 在面试时&#xff0c;经过寒暄后&#xff0c;一般面试官会让介绍项目经验 。常见的问法是…

Go C 编程 第4课 变色魔法(魔法学院的奇幻之旅 有Go C编程绘图)

慧通教育 慧通教育 34.画彩色旗帜 (魔法学院第4课)--2022.12.15 登录 35.画转动的方形 (魔法学院第4课) 登录 36.画wifi信号 (魔法学院第4课) 登录 888.哪个大&#xff1f; (课程6&#xff09; 难度&#xff1a;1 登录 889.余数大小 (课程6) 难度&#xff1a;1 登录 适合…

Java学习—网络编程

网络编程 目的&#xff1a;数据交换、通信 1. 网络通信的要素 通信双方地址&#xff08;IP端口号&#xff09;网络通信协议 Java万物皆对象 2. IP地址 IP地址的类&#xff1a;InetAddress 唯一定位一台网络上的计算机127.0.0.1 本机地址 package com.xiaozhang.lesson01;…

第二证券|钠电池三种技术路线谁更将率先取代锂电池?

锂电网讯&#xff1a;近段时刻&#xff0c;钠离子电池遭到资本商场重视&#xff0c;不少公司公布了在钠电池范畴的最新进展。11月29日&#xff0c;中科海钠(阜阳)全球首条GWh级钠离子电池出产线产品下线。中科海钠总经理李树军透露&#xff0c;阜阳产线计划在下一年扩产至3GWh-…

大数据学习:shell基础(2)

文章目录tail命令选项参数任务一&#xff1a;显示文件最后4行内容任务二&#xff1a;显示文件最后4个字符内容任务三&#xff1a;显示文件修改行sort命令选项参数任务一&#xff1a;对文件按行排序任务二&#xff1a;对文件按第4节排序cut命令参数说明任务一&#xff1a;提取ip…

想要查询数据表中的前几名该怎么实现?看这篇文章吧

一. 需求分析 我们在学习数据库查询时&#xff0c;经常会遇到关于分组和聚合函数的查询&#xff0c;比如查询每门课程的最高分&#xff0c;每位同学的平均分&#xff0c;其实这些都是比较一般的问题。但如果遇到查询每门课程成绩的前几名问题&#xff0c;就会变的很棘手&#…

【深度学习】Tensorflow、MindSpore框架介绍及张量算子操作实战(超详细 附源码)

一、Tensorflow、MindSpore Google公司于2015年开源了深度学习框架TensorFlow&#xff0c;推动了深度学习的发展&#xff0c;得到了广泛应用&#xff0c;用户数量庞大。 华为公司于2020年开源了自己的深度学习框架MindSpore&#xff0c;现处于快速发展中。 TensorFlow2深度学…

直播 | 新一代极速云原生湖仓的技术内核,StarRocks PMC 今天下午为你揭秘!

12 月 14-16 日&#xff0c;第 13 届中国数据库技术大会&#xff08;DTCC 2022&#xff09;将在线上隆重召开。本届大会重点围绕云原生数据库、分布式数据库、时序数据库、图数据技术、实时数仓技术与应用实践、金融业数据库应用实践等内容展开分享和探讨。 12 月 14 日 14:20…

ESP32 与 ESP32-CAM 的关系

ESP32 与 ESP32-CAM 的关系 以下分别介绍 ESP32 与 ESP32-CAM&#xff0c;两者之间的关系可以简单用下表来说明&#xff1a; ESP32ESP32-CAM开发公司乐鑫信息科技安信可科技模块关系ESP32 系列 ( 共有ESP32-S 系列、ESP32-C 系列与ESP32 系列)ESP32 系列中的ESP32-WROVER 模组…

(九)Java网络编程无冕之王-这回把大名鼎鼎的Netty框架一网打尽!

引言 现如今的开发环境中&#xff0c;分布式/微服务架构大行其道&#xff0c;而分布式/微服务的根基在于网络编程&#xff0c;而Netty恰恰是Java网络编程领域的无冕之王。Netty这个框架相信大家定然听说过&#xff0c;其在Java网络编程中的地位&#xff0c;好比JavaEE中的Sprin…

Java中的二维数组

一、介绍 应用场景&#xff1a;比如我们要开发一个五子棋游戏&#xff0c;期盼就是需要用二维数组来表示。即一维数组的每一个元素也是数组。 例子&#xff1a;请用二维数组输出如下图形&#xff1a; 000000 001000 020300 000000 package com.hspedu.array;public class Tw…

Java做UI自动化和app自动化中动态代理@FindBy的工作原理【杭州多测师_王sir】【杭州多测师】...

Java做UI自动化和app自动化中动态代理FindBy的工作原理一、背景简介由于Selenium框架采用PageObject设计模式让测试代码与被测页面对象代码分离&#xff0c;因而提供了不少很方便的注解来达到目的&#xff0c;其中有一个注解就是FindBy。在使用中&#xff0c;只要通过在field中…

ruoyi 在页面上增加一个显示字段(数据库增加字段,页面后端处理)

例如&#xff0c;上图所示&#xff0c; 【用户工资表】现有字段为用户id&#xff0c;用户工资&#xff0c;生效时间&#xff0c;备注信息 这些字段也就是你设计数据库时&#xff0c;数据库工资表字段 &#xff0c;现在&#xff0c;要让显示这个页面&#xff0c;增加一个用户姓…

Flutter for Web 首次首屏优化——JS 分片优化

作者&#xff1a;马坤乐(坤吾) Flutter for Web&#xff08;FFW&#xff09;从 2021 年发布至今&#xff0c;在国内外互联网公司已经得到较多的应用。作为 Flutter 技术在 Web 领域的有力扩充&#xff0c;FFW 可以让熟悉 Flutter 的客户端同学直接上手写 H5&#xff0c;复用 A…