之前在A*算法演示程序的編碼過程中,發現javaScript并沒有原生的優先隊列,于是去Java中找到了PriorityQueue類,研究了一下源碼。Java中的優先隊列基于最小二叉堆實現。最小二叉堆具有兩個性質:
結構性:必須是一顆完全二叉樹,樹的插入從左到右,一棵完全二叉樹的高度為小于log(N)的最大整數。
堆序性:二叉樹的父節點必須小于兩個子節點。父節點下標為n,兩個子節點下標為2n+1和2(n+1)。父節點的值總是小于子節點,兩個子節點間大小關系不確定。
Java PriorityQueue特點:
不接受null值
不接受不可排序的值
線程不安全
容量沒有上限
插入和刪除元素的時間復雜度為O(log(n))
首先來看下最關鍵的兩個方法,siftDown(int k, E x)和siftUp(int k, E x)。這兩個方法是插入、刪除、排序和初始化操作的基礎。兩個方法的目的都是讓節點k所在的子樹符合二叉堆的性質,堆序性。區別在于,siftDown將節點k作為子樹的父節點處理,siftUp則將節點k作為子樹的子節點處理。
siftDown
private void siftDownComparable(int k, E x) { Comparablekey = (Comparable)x; int half = size >>> 1; //k) 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; }~~~關于這部分代碼,比較有意思的是迭代條件`k>>1`,我們分size為奇數和偶數兩種情況來看:* 奇數:size為奇數,`half=(size-1)/2`,最后一個節點下標為`size-1`,父節點下標為`(size-1)/2-1`,所以`kkey = (Comparable) 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;
}
siftUp的代碼很容易理解,遞歸的與父節點比較大小,然后根據結果決定是否需要對調位置。
heapify
接下來是PriorityQueue的初始化方法中最復雜的一個,從一個無需集合(Collection)生成一個優先隊列。
private void initFromCollection(Collection c) {
initElementsFromCollection(c);
heapify();
}
initElementsFromCollection(c)方法很簡單,將集合c中的數據放入隊列,將集合c的大小賦予size屬性。而heapify()方法就是將集合c轉換為二叉堆的關鍵。
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
size>>>1
的特殊性我們剛才已經討論過了,這里看下heapify()是怎么做的,從(size>>>1)-1
開始向上遍歷,對每一個父節點,都要保證它所在的子樹符合條件,那么整棵樹都將符合條件。
add offer
向隊列中添加元素,兩者代碼相同,之所以并存是因為PriorityQueue繼承自Queue。
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;
}
這里將元素放于隊列尾部,調用siftUp方法進行調整。最壞的情況,新插入的元素比根節點的元素還要小,那么siftUp要執行到根節點所在的子樹。
removeAt
刪除隊列中下標為i的元素。由于要保持完整二叉樹的結構,刪除元素后仍需要進行重新排序。
private E removeAt(int i) {
modCount++;
int s = --size;
//如果i就是最后一個節點的下標,直接刪除
if (s == i)
queue[i] = null;
else {
E moved = (E) queue[s];
queue[s] = null;
siftDown(i, moved);
if (queue[i] == moved) {
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}