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. PHP常用的自定义函数

    PHP常用的自定义函数 目录 php常用自定义函数类下载 php 设置字符编码为utf-8 路径格式化(替换双斜线为单斜线) 转码 打印输出 api返回信息 字符串截取 方法一: 方法二: 数组 字符 ...

  2. Python元组,列表,字典,集合

    1.元组 元组是有序的,只有index和count两种方法,一看到元组,就提醒是不可更改的 names = ('wll', 'ly', 'jxx', 'syq') (1)index方法 print(n ...

  3. C++基础 namespace register bool

    1. namespace (1)命令空间 C中的命名空间 在C中只有一个全局作用域 C语言中所有全局标识符共享同一个作用域 标识符之间可能发生冲突 C++中提出了命名空间的概念 命令空间将全局作用域分 ...

  4. POJ-3126 BFS,埃式筛选及黑科技

    题目大意:给定两个四位素数a  b,要求把a变换到b,变换的过程要保证  每次变换出来的数都是一个 四位素数,而且当前这步的变换所得的素数  与  前一步得到的素数  只能有一个位不同,而且每步得到的 ...

  5. 笔记-python-lib-requests常用类/方法/属性

    笔记-python-lib-requests常用类/方法/属性 1.      requests模块常用类/方法/属性 在使用中发现对requests模块不够熟悉,写了几个案例后重新整理了一下文档,罗 ...

  6. CSS3裁剪与遮罩解析

    一.用途 CSS3裁剪与遮罩(Clipping and Masking)用来隐藏元素的一部分而显示另一部分 二.区别 CSS3裁剪与遮罩(Clipping and Masking)用来隐藏元素的一部分 ...

  7. Thread-local storage (TLS)

    线程存储原理:为变量在每一个现存的线程里分配一个实例,需要处理器支持,并不是所有都支持!支持全局的,静态的变量,但不支持局部变量. 关键字 __thread   __thread int i;   e ...

  8. Internet接入方式

    (转载) 接入网可以大概分成两类:拨号上网(包括ASDL)与专线上网 在接入网中,目前可供选择的接入方式主要有PSTN.ISDN.DDN.LAN.ADSL.VDSL.Cable-Modem.PON和L ...

  9. javascript类式继承模式#2——借用构造函数

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. WIN10把照片查看器设为默认看图软件

    WIN10默认是PHOTO,没有以前WIN7的照片查看器好用,要改回来的方法如下:   在注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Photo ...