PriorityQueue是一种可以高效删除最小元素的集合。其特点如下:
- PriorityQueue是一个基于优先级堆的无界优先级队列(容量不限制)
- PriorityQueue中的元素按照元素的自然排序或者在构造对象时提供的比较器进行比较排序
- PriorityQueue不能保存null值
- PriorityQueue中的排序是按照元素自身的自然排序,则不能保存没有实现Compareable接口的元素
- PriorityQueue中第一个元素为通过给定的排序方法对元素进行排序后最小的元素
PriorityQueue既然是一种可以高效删除最小元素的集合,它至少应该满足一下几个条件:
- 获取(删除)最小元素时明确指出最小元素的位置
- 在删除最小元素之后如何快速的将第二小的元素设置为最小元素
- 每次插入新的元素时判断该元素是否为最小元素
为什么满足上面的条件,PriorityQueue使用了数组保存元素,并将第一个元素定义为最小元素,在插入元素时采用堆排序(时间复杂度为O(log n))
对于堆排序其实可以理解为利用了树的一种数据结构。一般我们保存树是用下面的数据结构
public class TreeNode<K> {
private K value;
private TreeNode<K> parent;
private TreeNode<K> leftChild;
private TreeNode<K> rightChild;
}
表现的内容如下:
除了上面的表现形式,还可以通过数组来表示,如下图
在上面这种表现形式中,需要注意的是通过当前元素的索引来查到到父亲元素的索引的方法为(当前索引/2),通过当前元素的索引查找子元素索引的方法为(当前索引*+1),这个获取的是左子元素,右子元素只需要加1即可。
PriorityQueue是父节点的值肯定小于子节点这个特点来实现的。下面来看看PriorityQueue中是如何实现插入跟删除的。
在插入一个新元素时,通过新元素的索引获取的父节点的索引,将两者进行比较,如果父节点的值大于新节点,将新节点的值设置为父节点的值,以此类推,父节点继续跟它的父节点比较直到当前节点的值大于父节点的值,或者当前节点为根节点。流程如下:
代码实现如下:
@SuppressWarnings("unchecked")
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;
}
在删除元素时,PriorityQueue是删除数组中的第一个元素,然后将最后一个元素设置为第一个元素,接着向下比较并修改元素的位置。
代码如下
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;
}