PriorityQueue介绍

在平时的编程工作中似乎很少碰到PriorityQueue(优先队列) ,故很多人一开始看到优先队列的时候还会有点迷惑。优先队列本质上就是一个最小堆。前面一篇文章介绍了堆排序和堆的性质。而堆又是什么呢?它是一个数组,不过满足一个特殊的性质。我们以一种完全二叉树的视角去看这个数组,并用二叉树的上下级关系来映射到数组上面。如果是最大堆,则二叉树的顶点是保存的最大值,最小堆则保存的最小值。

下面是一个典型的优先队列的结构图:

它的每个父节点都比两个子节点要小,但是整个数组又不是完全顺序的。

有了前面的这些铺垫,我们再来看它的整体结构和各种调整过程。

建堆

PriorityQueue内部的数组声明如下:

private transient Object[] queue;  

private static final int DEFAULT_INITIAL_CAPACITY = ; 

它默认的长度为11. 建堆的过程中需要添加新的元素,

PriorityQueue的建堆过程和最大堆的建堆过程基本上一样的,从有子节点的最靠后元素开始往前,每次都调用siftDown方法来调整。这个过程也叫做heapify。

private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
} 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;
} private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

siftDown的过程是将一个节点和它的子节点进行比较调整,保证它比它所有的子节点都要小。这个调整的顺序是从当前节点向下,一直调整到叶节点。

siftDown的过程如下图所示:

因为一开始要建堆的时候,里面的元素是杂乱无章的,所以调整前可能的结构如下:

假设我们siftDown的元素是9,则它先和它的左右子节点进行比较,然后和最小的子节点交换位置:

经过一次交换之后,发现它还有子节点,而且子节点比它小,所以需要继续交换。

按照这个思路来理解,前面的过程就很明了了。

扩展

堆里面的数组长度不是固定不变的,如果不断往里面添加新元素的时候,也会面临数组空间不够的情形,所以也需要对数组长度进行扩展。这个扩展方法的源头就是由插入元素引起的。

数组长度扩展的方法如下:

private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
} private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

这部分代码和ArrayList的内部实现代码基本相同,都是先找到合适的数组长度,然后将元素从旧的数组拷贝到新的数组。

而添加新元素的过程如下:

每次添加新的元素进来实际上是在数组的最后面增加。在增加这个元素的时候就有了判断数组长度和调整的一系列动作。等这些动作调整完之后就要进行siftUp方法调整。这样做是为了保证堆原来的性质。

siftUp的过程可以用如下图来描述:

假设我们在后面添加了元素3,那么我们就要将这个元素和它的父节点比较,如果它比它的父节点小,则要交换他们的顺序。这样一直重复到它大于等于它的父节点或者到根节点。

经过调整之后,则变成符合条件的最小堆:‘

它的详细实现代码如下:

private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(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];
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];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}

注意事项:

我们看前面的调整方法不管是siftUp还是siftDown都用了两个方法,一个是用的comparator,还有一个是用的默认比较结果。这样做的目的是考虑到我们要比较的元素不仅仅是数字等类型,也有可能是被定义了可比较的数据类型。对于自定义的数据类型,他们的大小比较定义需要实现comparator接口。至于使用comparator接口的意义背后的思想,可以参照我的这一篇博文

总结

PriorityQueue本质上就是堆排序里面建的最小堆。最小堆满足的一个基本性质是堆顶端的元素是所有元素里最小的那个。如果我们将顶端的元素去掉之后,为了保持堆的性质,需要进行调整。对堆的操作和调整主要包含三个方面,增加新的元素,删除顶端元素和建堆时保证堆性质的操作。前面讨论堆排序的文章已经有了一个详细的介绍。这里主要结合jdk的类库实现,看看一个用于实际生产环境的优先队列实现。 另外,PriorityQueue在一些经典算法中也有得到应用,相当于是它们实现的基础。

参考材料

http://shmilyaw-hotmail-com.iteye.com/blog/1775868

java集合类深入分析之PriorityQueue(二)的更多相关文章

  1. java集合类深入分析之Queue篇

    简介 Queue是一种很常见的数据结构类型,在java里面Queue是一个接口,它只是定义了一个基本的Queue应该有哪些功能规约.实际上有多个Queue的实现,有的是采用线性表实现,有的基于链表实现 ...

  2. java集合类深入分析之Queue篇(Q,DQ)

    简介 Queue是一种很常见的数据结构类型,在java里面Queue是一个接口,它只是定义了一个基本的Queue应该有哪些功能规约.实际上有多个Queue的实现,有的是采用线性表实现,有的基于链表实现 ...

  3. java集合类的学习(二)

    ArrayList,LinkedList,Vector都是List的实现类,前两都没有实现同步机制,Vector实现了同步机制.他们代码类似. ArrayList代表大小可变的数组,允许对元素进行快速 ...

  4. java集合类源码学习二

    我们查看Collection接口的hierarchy时候,可以看到AbstractCollection<E>这样一个抽象类,它实现了Collection接口的部分方法,Collection ...

  5. java集合类(二)List学习

    接上篇  java集合类(一) List接口继承了Collection接口和Iterable接口,即同样含有Collection和 Iterable的特性,还有方法,其基本方法有: 1)有关添加: b ...

  6. 一张图让你看清Java集合类(Java集合类的总结)

    如今关于Java集合类的文章非常多,可是我近期看到一个非常有意思图片,基本上把Java集合的整体框架都给展现出来了.非常直观. watermark/2/text/aHR0cDovL2Jsb2cuY3N ...

  7. 好看的java集合类图

    http://blog.csdn.net/iamzp2008/article/details/38151971?utm_source=tuicool&utm_medium=referral 现 ...

  8. Java集合类——Set、List、Map、Queue接口

    目录 Java 集合类的基本概念 Java 集合类的层次关系 Java 集合类的应用场景 一. Java集合类的基本概念 在编程中,常需要集中存放多个数据,数组是一个很好的选择,但数组的长度需提前指定 ...

  9. 201871010124--王生涛--《面向对象程序设计(java)》第十二周学习总结

    博文正文开头格式: 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/nw ...

随机推荐

  1. python requests第三方库详解

    异常处理:try ... except ...

  2. WPF制作的小时钟

    原文:WPF制作的小时钟 周末无事, 看到WEB QQ上的小时钟挺可爱的, 于是寻思着用WPF模仿着做一个. 先看下WEB QQ的图: 打开VS, 开始动工. 建立好项目后, 面对一个空荡荡的页面, ...

  3. Java线程和多线程(六)——守护线程

    当我们在Java中创建线程的时候,这个线程在默认的情况下是一个用户线程,并且,如果这个线程在运行,那么JVM就不会终结这个应用.和用户线程不同,当一个线程被标记为守护线程的时候,JVM在用户线程结束的 ...

  4. global js库

    var GLOBAL = {}; GLOBAL.namespace = function(str) { var arr = str.split("."), o = GLOBAL,i ...

  5. git bash中文显示为数字

    转自:http://blog.csdn.net/zhujiangtaotaise/article/details/74424157 今天让去做一个另外一个项目,导入项目后,git status 发现原 ...

  6. GLIBCXX3.4.21 not find

    在执行世界杯的二进制代码和安装keepaway中会遇到GLIBCXX3.4.21 not find的问题,其解决办法就是升级安装GCC. 一.首先查看当前gcc版本 $ strings /usr/li ...

  7. 解决display:inline-block;行内块元素出现空白空隙问题

     给他的父元素加:font-size:0px;,   ul { font-size:0px;} li { font-size:16px;}

  8. 5.修改haproxy配置文件

    需求: 1.查 输入:www.oldboy.org 获取当前backend下的所有记录 2.新建 输入: arg = { 'backend': 'www.oldboy.org', 'record':{ ...

  9. 聊聊、Spring WebApplicationInitializer

    说到 WebApplicationInitializer,这个接口是为了实现代码配置 Web 功能.只要实现了这个接口,那么就可以实现 Filter,Servlet,Listener 等配置,跟在 x ...

  10. CSLA多语言设置

    1.在程序运行文件夹例如“\Bin\Debug\”中包含csla生成的资源文件: 2.在程序运行时,设置CSLA的当前语言为你想要的语言,例如:Csla.Properties.Resources.Cu ...