转载自  深入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元素

深入Java集合系列之五:PriorityQueue相关推荐

  1. Java 集合系列02之 Collection架构

    概要 首先,我们对Collection进行说明.下面先看看Collection的一些框架类的关系图: Java 集合系列02之 Collection架构 Collection是一个接口,它主要的两个分 ...

  2. Java 集合系列06: Vector深入解析

    戳上面的蓝字关注我们哦! 精彩内容 精选java等全套视频教程 精选java电子图书 大数据视频教程精选 java项目练习精选 概论 这是接着以前的文章分享的,这里给出以前的文章的连接,供小伙伴们回顾 ...

  3. Java 集合系列(一)

    Java集合系列文章将以思维导图为主要形式来展示知识点,让零碎的知识形成体系. 这篇文章主要介绍的是[Java 集合的基本知识],即Java 集合简介. 毕业出来一直使用 PHP 进行开发,对于大学所 ...

  4. Java 集合系列04之 fail-fast总结

    转载自   Java 集合系列04之 fail-fast总结 概要 前面,我们已经学习了ArrayList.接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解.内 ...

  5. Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

    转载自  Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 第1部分 ArrayList介绍 ArrayList简介 ArrayList 是一个数组队列,相当于 动态数组.与 ...

  6. Java 集合系列目录(Category)

    Java 集合系列目录(Category) 转自:Java 集合系列目录(Category) 01. Java 集合系列01之 总体框架 02. Java 集合系列02之 Collection架构 0 ...

  7. Java集合系列之四大常用集合(ArrayList、LinkedList、HashSet、HashMap)的用法

    Java集合系列之四大常用集合(ArrayList.LinkedList.HashSet.HashMap)的用法 ArrayList ArrayList就是传说中的动态数组,用MSDN中的说法,就是A ...

  8. Java 集合系列 16 HashSet

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  9. java集合系列——java集合概述(一)

    在JDK中集合是很重要的,学习java那么一定要好好的去了解一下集合的源码以及一些集合实现的思想! 一:集合的UML类图(网上下载的图片) Java集合工具包位置是java.util.* 二:集合工具 ...

最新文章

  1. kvm cpu的亲和性绑定配置
  2. 都是“工作惯性”惹的祸
  3. 安全漏洞“心脏出血”继续 原因是“丘比特”
  4. 关于git fetch 和git pull 的区别
  5. 【OS学习笔记】十九 保护模式六:保户模式下操作系统内核如何加载用户程序并运行
  6. C++:求n以内被3除余余1的所有整数
  7. 荣耀50 Pro+配置参数曝光:AMOLED高刷屏+骁龙888旗舰芯片
  8. php orm教程,Laravel ORM 数据model操作教程
  9. log4j的日志级别
  10. Intellij IDEA连接Spark集群
  11. python实时处理log文件脚本
  12. c++位运算_最全位运算总结
  13. 《DirectX 游戏开发终极指南》游戏项目编译成功的方法
  14. 在endnote中制作GB/T7714《文后参考文献著录规则》的输出格式 及 编辑Output Styles中特殊符号说明
  15. XiaoZi's CrackMe
  16. OpenWrt mesh组网设置
  17. linux安装程序企鹅,Linux-小企鹅输入法的安装与使用
  18. Web前端优化最佳实践及工具集锦(如减少页面加载时间)
  19. Centos6 安装yum 完美安装(转载)
  20. 指向vector的指针

热门文章

  1. Jdbc创建表 利用循环添加数据 ,更新数据
  2. 紧跟月影大佬的步伐,一起来学习如何写好JS(上)
  3. [mybatis]逆向工程MGB基本编写
  4. [JavaWeb-HTTP]HTTP_请求消息_请求头请求体
  5. [JavaWeb-HTML]HTML标签(大部分常用标签介绍)
  6. 高等数学上-赵立军-北京大学出版社-题解-练习3.4
  7. 怎么创建dllwenjian_如何创建和使用dll及lib文件
  8. 统计学习笔记(1) 监督学习概论(1)
  9. 按照前序遍历和中序遍历构建二叉树
  10. P5273 【模板】多项式幂函数 (加强版)