深入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,一经查实,立即删除!

相关文章

linux系统操作大全,Linux系统的常用操作命令大全

From:http://www.xiaoxiaozi.com/2010/11/09/1985/摘自&#xff1a;http://hi.baidu.com/jackywdx/blog/item/393ccf4b64268bf482025cde.html系统# uname -a # 查看内核/操作系统/CPU信息# head -n 1 /etc/issue # 查看操作系统版本# cat /proc/cpuinfo # 查看CPU信息# hostnam…

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

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

struts+hibernate+oracle+easyui实现lazyout组件的简单案例——hibernate的config文件(hibernate.cfg.xml)

hibernate.cfg.xml文件&#xff0c;必不可少的一个xml文件&#xff0c;上面附有数据库的用户名&#xff0c;密码&#xff0c;链接字符串&#xff0c;方言等信息&#xff0c;还包含映射的文件路径&#xff1a; <?xml version1.0 encodingUTF-8?> <!DOCTYPE hibernate…

微服务的前世今生

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

linux 查看本机网关地址,linux查看服务器网关地址

弹性云服务器 ECS弹性云服务器(Elastic Cloud Server)是一种可随时自助获取、可弹性伸缩的云服务器&#xff0c;帮助用户打造可靠、安全、灵活、高效的应用环境&#xff0c;确保服务持久稳定运行&#xff0c;提升运维效率三年低至5折&#xff0c;多种配置可选了解详情什么是弹性…

非阻塞线程安全列表——ConcurrentLinkedDeque应用举例

转载自 非阻塞线程安全列表——ConcurrentLinkedDeque应用举例 在java中,最常用的数据结构可能是列表。有数目不详的元素列表,你可以添加、阅读、或删除任何位置的元素。此外,并发列表允许不同的线程列表中添加或删除元素时不产生任何数据不一致。非阻塞列表提供如下操作,如果…

整合JDBC---SpringBoot

整合JDBC SpringData简介 对于数据访问层&#xff0c;无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库)&#xff0c;Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。 Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库&#xff0c;Sprin…

struts+hibernate+oracle+easyui实现lazyout组件的简单案例——EmpDao层代码

严格按照三层架构来写的&#xff0c;Dao层的代码比较少&#xff0c;我直接把Emp和实现类的都放在这篇文章里面吧&#xff0c; IEmpDao.java接口&#xff1a; /** * Title: IEmpDao.java * Package org.dao * Description: TODO该方法的主要作用&#xff1a; * author A18ccm…

在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;&#…

java并发编程(二十一)----(JUC集合)CopyOnWriteArraySet和ConcurrentSkipListSet介绍

转载自 java并发编程&#xff08;二十一&#xff09;----(JUC集合)CopyOnWriteArraySet和ConcurrentSkipListSet介绍 这一节我们来接着介绍JUC集合&#xff1a;CopyOnWriteArraySet和ConcurrentSkipListSet。从名字上来看我们知道CopyOnWriteArraySet与上一节讲到的CopyOnWrit…

linux u32,如何在程序中使用u32这个类型啊。

如何在程序中使用u32这个类型啊。 我用的keil 4.5#include "stm32f10x.h"int main(void){GPIO_InitTypeDef GPIO_Init1;GPIO_Init1.GPIO_Pin GPIO_Pin_0;GPIO_Init1.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init1.GPIO_Mode GPIO_Mode_Out_PP;GPIO_Init(GPIOA,&GPI…

虚拟机安装CentOS-7-x86_64-DVD-1708说明

https://blog.csdn.net/guanzhuwo/article/details/105903844 镜像已经下载了 使用IDM下载 http://mirrors.aliyun.com/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-2003.iso 虚拟机安装CentOS-7-x86_64-DVD-1708说明 guanzhuwo 2020-05-03 15:12:22 102 收藏 分类专栏&a…

struts+hibernate+oracle+easyui实现lazyout组件的简单案例——DeptDao层代码

下面来看看DeptDao 的事例吧&#xff1a; IDeptDao.java: /** * Title: IDeptDao.java * Package org.dao * Description: TODO该方法的主要作用&#xff1a; * author A18ccms A18ccms_gmail_com * date 2017-4-19 下午6:37:00 * version V1.0 */ package org.dao;impo…

IJ实现侧边栏单独搜索

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

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

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

Java并发包:ConcurrentMap

转载自 Java并发包&#xff1a;ConcurrentMap 文章译自&#xff1a;http://tutorials.jenkov.com/java-util-concurrent/index.html 抽空翻译了一下这个教程的文章&#xff0c;后面会陆续放出&#xff0c;如有不妥&#xff0c;请批评指正。 转自请注明出处。 之前漏了一篇文…

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;同时加入了日志…

struts+hibernate+oracle+easyui实现lazyout组件的简单案例——工具类

次此篇文章是两个工具类的实现&#xff0c;HibernateUtil.java和分页的工具类PageUtil.java的实现&#xff1a; 先看看HibernateUtil.java的实现&#xff1a; package org.util;import javax.transaction.Transaction;import org.hibernate.Session; import org.hibernate.Sess…