深入Java集合系列之五:PriorityQueue

转载自  深入Java集合系列之五:PriorityQueue

 

前言

今天继续来分析一下PriorityQueue的源码实现,实际上在Java集合框架中,还有ArrayDeque(一种双端队列),这里就来分析一下PriorityQueue的源码。PriorityQueue也叫优先队列,所谓优先队列指的就是每次从优先队列中取出来的元素要么是最大值(最大堆),要么是最小值(最小堆)。我们知道,队列是一种先进先出的数据结构,每次从队头出队(移走一个元素),从队尾插入一个元素(入队),可以类比生活中排队的例子就好理解了。 

PriorityQueue说明

PriorityQueue底层实现的数据结构是“堆”,堆具有以下两个性质:

任意一个节点的值总是不大于(最大堆)或者不小于(最小堆)其父节点的值;堆是一棵完全二叉树

而优先队列在Java中的使用的最小堆,意味着每次从队列取出的都是最小的元素,为了更好理解源码,有必要了解堆的一些数字规律。我们知道无论堆还是其他数据结构,最终都要采用编程语言加以实现,在Java中实现堆这种数据结构归根结底采用的还是数组,但这个数组有点特殊,每个数组中的元素的左右孩子节点也存在该数组中,对于任意一个数组下标i,满足:

左孩子节点的下标left(i)=2*i,右孩子节点right(i) = 2*i+1

这样的话就可以把数据结构中复杂的树形元素放在简单的数组中了,只要按照上面的规律就可以很方便找到任意节点的左右孩子节点。解决完元素的存储问题还要把数组中的元素还原为堆,这就是建堆的过程,后面的源码也是基于同样的思想。以每次向堆中添加一个元素为例,由于使用数组存储,新添加的元素的下标是数组的最后一个下标值,对应到堆中就是堆中最后一个叶子节点,由于新添加元素破坏了堆的性质,所以需要对新的添加的元素做调整,使其移动到正确的位置,使得堆重新符合堆的性质。

那么问题来了,从哪个位置开始建堆呢?我们注意到最后一个节点的父节点是拥有孩子节点的下标最大的节点,因为叶子节点没有孩子节点,基于这点考虑我们选择最后一个节点的父节点作为建堆的起点,对与每个节点来说,接着要做的就是调整节点的位置了,这是实现最大堆或者最小堆的关键,为了能形象说明建堆的过程,请参看下面的示意图:

下面以元素{6,5,3,1,4,8,7}为例,说明建堆的具体过程:

如果你觉得这个过程太单调,你可以参考下面的动态图,不过下面这个动态图还包括堆排序的内容,只需要关注前面建堆哪个动态图就好了。

好了,现在你应该了解了建堆的具体过程,下面的关键就是添加元素以及移除元素了,为了结合Priority的源码说明,我把这部分的内容留到源码分析了。

源码分析

入队

在分析入队之前,我们来看看Java源码是怎么建堆的?

//从插入最后一个元素的父节点位置开始建堆
private void heapify() {for (int i = (size >>> 1) - 1; i >= 0; i--)siftDown(i, (E) queue[i]);
}
//在位置k插入元素x,为了保持最小堆的性质会不断调整节点位置
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) {//得到k位置节点左孩子节点,假设左孩子比右孩子更小int child = (k << 1) + 1; // assume left child is least//保存左孩子节点值Object c = queue[child];//右孩子节点的位置int right = child + 1;//把左右孩子中的较小值保存在变量c中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;}//循环结束,k是叶子节点queue[k] = key;
}

ok,下面看看如何在一个最小堆中添加一个元素:

public boolean add(E e) {//调用offer函数return offer(e);
}
//siftUp之前的代码主要确认队列的容量不发生溢出,并保存队列中的元素个数以及发生结构//性修改的次数
public boolean offer(E e) {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;
}
//调用不同的比较器调整元素的位置
private void siftUp(int k, E x) {if (comparator != null)siftUpUsingComparator(k, x);elsesiftUpComparable(k, x);
}
//使用默认的比较器调整元素的位置
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];//使用compareTo方法,如果要插入的元素小于父节点的位置则交换两个节点的位置if (key.compareTo((E) e) >= 0)break;queue[k] = e;k = parent;}queue[k] = key;
}
//调用实现的比较器进行元素位置的调整,总的过程和上面一致,就是比较的方法不同
private void siftUpUsingComparator(int k, E x) {while (k > 0) {int parent = (k - 1) >>> 1;Object e = queue[parent];//这里是compare方法if (comparator.compare(x, (E) e) >= 0)break;queue[k] = e;k = parent;}queue[k] = x;
}

为了更好理解上面代码的执行过程,请参看下面的示意图:

出队

出队就是从队列中移除一个元素,我们看看在源码中实现:

private E removeAt(int i) {assert i >= 0 && i < size;modCount++;//s是队列的队头,对应到数组中就是最后一个元素int s = --size;//如果要移除的位置是最后一个位置,则把最后一个元素设为nullif (s == i) // removed last elementqueue[i] = null;else {//保存待删除的节点元素E moved = (E) queue[s];queue[s] = null;//先把最后一个元素和i位置的元素交换,之后执行下调方法siftDown(i, moved);//如果执行下调方法后位置没变,说明该元素是该子树的最小元素,需要执行上调方//法,保持最小堆的性质if (queue[i] == moved) {//位置没变siftUp(i, moved);   //执行上调方法if (queue[i] != moved)//如果上调后i位置发生改变则返回该元素return moved;}}return null;
}

在上面的代码上调方法与下调方法只会执行其中的一个,参看下面需要执行下调方法的示意图:

这是需要执行上调方法的示意图:

PriorityQueue小结

经过上面的源码的分析,对PriorityQueue的总结如下:

  • 时间复杂度:remove()方法和add()方法时间复杂度为O(logn),remove(Object obj)和contains()方法需要O(n)时间复杂度,取队头则需要O(1)时间
  • 在初始化阶段会执行建堆函数,最终建立的是最小堆,每次出队和入队操作不能保证队列元素的有序性,只能保证队头元素和新插入元素的有序性,如果需要有序输出队列中的元素,则只要调用Arrays.sort()方法即可
  • 可以使用Iterator的迭代器方法输出队列中元素
  • PriorityQueue是非同步的,要实现同步需要调用java.util.concurrent包下的PriorityBlockingQueue类来实现同步
  • 在队列中不允许使用null元素

 

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

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

相关文章

抹掉所有内容和设置 连接到icloud时出错 iphone还原出厂设置

设置-》抹掉所有内容和设置 点击设置 在顶部输入框中 输入抹掉 2个子就可以找到 1&#xff0c;点通用&#xff0c;存储用量&#xff0c;如果icloud为不可用&#xff0c; 设置--蜂窝数据-网络为无线网和蜂窝数据&#xff0c;成功的点个赞吧~&#xff01;&#xff08;这个过…

微服务的前世今生

译者&#xff1a;周元昊 与许多人认为的不同&#xff0c;微服务的概念已有相当长的历史&#xff0c;SOA&#xff08;面向服务的体系架构&#xff09;也不是90年代才被提出的。在最近举办的伦敦微服务大会上&#xff0c;Greg Young就微服务核心概念的前世今生进行了演讲。其中他…

在idea 中添加和删除模块Module

在idea 中添加和删除模块Module ThinkPet 2018-12-22 10:12:50 4125 收藏 1 分类专栏&#xff1a; idea 版权 1.添加模块 2.删除模块 ———————————————— 版权声明&#xff1a;本文为CSDN博主「ThinkPet」的原创文章&#xff0c;遵循CC 4.0 BY-SA版权协议&am…

ASP.NET Core File Providers

ASP.NET Core通过对File Providers的使用实现了对文件系统访问的抽象。 查看或下载示例代码 File Provider 抽象 File Providers是文件系统之上的一层抽象。它的主要接口是IFileProvider。IFileProvider公开了相应方法用来获取文件信息&#xff08;IFileInfo&#xff09;&#…

IJ实现侧边栏单独搜索

第一步任意点击一个 第二步输入要搜索的单词

关于全局ID,雪花(snowflake)算法的说明

C#版本的国外朋友已经封装了&#xff0c;大家可以去看看&#xff1a;https://github.com/ccollie/snowflake-net 强大的网友出来个简化版本&#xff1a;http://blog.csdn.net/***/article/details/*** &#xff08;地址我就不贴了&#xff0c;对前辈需要最起码的尊敬&#xff0…

SecureCRT的下载、安装( 过程非常详细!!值得查看)

我自己百度联通主号有存储了 可以下下来 有视频加这个文档 就可以了 https://blog.csdn.net/qq_39052513/article/details/100272502 SecureCRT的下载、安装&#xff08; 过程非常详细&#xff01;&#xff01;值得查看&#xff09; 置顶 超Ren专属 2020-06-02 21:29:33 1…

整合Druid---SpringBoot

整合Druid(数据源) Druid简介 Java程序很大一部分要操作数据库&#xff0c;为了提高性能操作数据库的时候&#xff0c;又不得不使用数据库连接池。 Druid 是阿里巴巴开源平台上一个数据库连接池实现&#xff0c;结合了 C3P0、DBCP 等 DB 池的优点&#xff0c;同时加入了日志…

理想的互联网服务后台框架的九个要点

理想的互联网服务后台框架的九个要点对于互联网服务后台团队&#xff0c;开发框架的选择是非常关键的一个问题&#xff0c;多年的海量服务经验和教训使得我们团队深刻的认识到&#xff1a; 要尽早规范团队的开发服务框架&#xff0c;避免到了后期&#xff0c;各种开发语言混杂、…

虚拟机安装centeros7 无法连接网络 virsh命令找不到 删除多余的vir0 不然dubbo会有问题

进入linux ping www.baidu.com 无法访问 cd /etc/sysconfig/network-scripts vi ifcfg-ens33 修改这个文件 onbootyes 原来是on shutdown -h now 关机 然后重启虚拟机 再次ping ping www.baidu.com 就通了 https://www.zhihu.com/question/53708440 virsh命令找…

Azure 部署 Asp.NET Core Web App

在云计算大行其道的时代&#xff0c;当你在部署一个网站时&#xff0c;第一选择肯定是各式各样的云端服务。那么究竟使用什么样的云端服务才能够以最快捷的方式部署一个 ASP.NET Core 的网站呢&#xff1f;Azure 的 Web App 服务是个很好的选择。 下面我们会通过 Visual Studio…

联想linux笔记本评测,联想(lenovo)G460AL-ITH Linux笔记本电脑CPU测试评测-ZOL中关村在线...

这颗英特尔最新的Core i5 430M双核心处理器基于32nm工艺&#xff0c;核心代号为Arrandate&#xff0c;主频为2.27GHz&#xff0c;共享的三级缓存为3MB。在开启睿频加速时&#xff0c;单核心的主频最高为2.53GHz&#xff0c;并且支持同步超线程技术。同时除了支持上一代处理器所…

SecureCRT连接Linux的操作步骤

https://www.cnblogs.com/Koma-vv/p/11100565.html SecureCRT连接Linux的操作步骤 虚拟机待机&#xff1a;Ctrlg进入 ipconfig是Windows里面的操作 ifconfig是Linux里面的操作 解决方法&#xff1a;右键&#xff1a; 打开终端是&#xff1a;在桌面上&#xff0c;鼠标右键才可…

聊聊并发(四)深入分析ConcurrentHashMap

转载自 聊聊并发&#xff08;四&#xff09;深入分析ConcurrentHashMap 术语定义 术语英文解释哈希算法hash algorithm是一种将任意内容的输入转换成相同长度输出的加密方式&#xff0c;其输出被称为哈希值。 哈希表hash table根据设定的哈希函数H(key)和处理冲突方法将一组…

IDEA 底部工具栏没有 Version Control 解决办法

IDEA 底部工具栏没有 Version Control 解决办法 百度了半天 都说VCS配置不对 但是默认IDEA是配置好的 根本不需要修改 忽然看到 工具栏的快捷键 于是 Alt 9 就出现了 完美

一个基于Microsoft Azure、ASP.NET Core和Docker的博客系统

2008年11月&#xff0c;我在博客园开通了个人帐号&#xff0c;并在博客园发表了自己的第一篇博客。当然&#xff0c;我写博客也不是从2008年才开始的&#xff0c;在更早时候&#xff0c;也在CSDN和系统分析员协会&#xff08;之后名为“希赛网”&#xff09;个人空间发布过一些…

聊聊并发-Java中的Copy-On-Write容器

转载自 聊聊并发-Java中的Copy-On-Write容器 Copy-On-Write简称COW&#xff0c;是一种用于程序设计中的优化策略。其基本思路是&#xff0c;从一开始大家都在共享同一个内容&#xff0c;当某个人想要修改这个内容的时候&#xff0c;才会真正把内容Copy出去形成一个新的内容然后…

集成SpringSecurity---SpringBoot

集成SpringSecurity 安全简介 在 Web 开发中&#xff0c;安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求&#xff0c;但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题&#xff0c;就可能陷入一个两难的境地&#xff1a;一方面&am…

大新闻!Magic Leap造假,HoloLens即将入华商用

昨天微软搞了大新闻&#xff0c;Terry和Alexi到了深圳&#xff0c;在WinHEC大会上宣布了2017上半年HoloLens正式入华商用。 而唯一竞争对手Magic Leap今天也被曝光其设备造假&#xff0c;各大科技媒体纷纷报道&#xff0c;部分相关报道如下&#xff1a; 【新浪科技】Magic Lea…

集成Swagger(API)---SpringBoot

集成Swagger(API) 学习目标: 了解Swagger的概念及作用掌握在项目中集成Swagger自动生成API文档Swagger简介 前后端分离 前端 -> 前端控制层、视图层后端 -> 后端控制层、服务层、数据访问层前后端通过API进行交互前后端相对独立且松耦合产生的问题 前后端集成,前端或者…