JDK源码分析实战系列-PriorityQueue
作者:互联网
PriorityQueue
Priority queue represented as a balanced binary heap: the two children of queue[n] are queue[2n+1] and queue[2(n+1)]
The element with the lowest value is in queue[0], assuming the queue is nonempty
优先级队列在JDK中有一个教科书式的示范实现,以上是JDK源码对实现的注释。和前面介绍的完全二叉树一样,存储元素时使用的父子节点在数组中的下标使用[2n+1] 和[2(n+1)]的公式计算,如果是反过来算父节点的下标位置公式是:(n-1)>>> 1
。
PriorityQueue就是一个小顶堆的实现,也是被认为实现优先级队列最高效的方式。
下面就是对PriorityQueue的实现分析。
插入元素操作
插入元素的时候先判断了是否需要扩容,扩容在后面会提到,核心的逻辑是,元素先加到队尾,然后进行siftUp
,使得新加入的元素调整成符合小顶堆的要求。
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
// 扩容
grow(i + 1);
// 最后一个位置下标加1
size = i + 1;
if (i == 0)
// 第一个元素情况
queue[0] = e;
else
// 节点从尾部加入,然后上移操作,直到保持堆处于正确状态
siftUp(i, e);
return true;
}
PriorityQueue支持元素实现Comparable
,也支持初始化时传入一个Comparator
作为比较算子。所以siftUp
区分了两种实现,都是差不多的,选一个分析一下,siftUp
是调整堆的核心操作,这个操作是把元素从参数k位置开始,和父节点进行比较,如果比父节点小,就和父节点交换,不断重复,直到父节点比自己大或等于,或者自己已经移动到根节点才停止。
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改为父节点下标,下一轮循环从父节点开始
k = parent;
}
// 退出循环出来的,直接把值赋值给k下标即可
queue[k] = key;
}
每增加一个元素,PriorityQueue就需要调整一次以确保小顶堆的排序,写操作是有一定消耗的。
结合siftUp方法实现
查询元素
查询操作其实就是遍历数组找出元素,不要感到惊讶,就是这么朴素无华,本质原因PriorityQueue的特性并不是快速定位某个元素的。是这里可能会有误解以为堆这种数据结构保证了左节点必然小于或大于右节点这样的规则,那么查找一个值可以是更有效率的二分法方式,而事实上堆并没有这个特性,所以查询一个元素就需要直接遍历全部元素。