PriorityQueue優先佇列實現原理
阿新 • • 發佈:2019-02-01
一、什麼是優先佇列
優先佇列不是按照普通物件先進先出原FIFO則進行資料操作,其中的元素有優先順序屬性,優先順序高的元素先出隊。本文提到的PriorityQueue佇列,是基於最小堆原理實現。
需要注意:PriorityQueue繼承了AbstractQueue沒有實現BlockingQueue介面,所以沒有take阻塞方法。
二、什麼是最小堆
最小堆是一個完全二叉樹,所謂的完全二叉樹是一種沒有空節點的二叉樹。
最小堆的完全二叉樹有一個特性是根節點必定是最小節點,子女節點一定大於其父節點。還有一個特性是葉子節點數量=全部非葉子節點數量+1
在 PriorityQueue佇列中,基於陣列儲存了完全二叉樹。所以在已知任意一個節點在陣列中的位置,就可以通過一個公式推算出其左子樹和右子樹的下標。已知節點下標是i,那麼他的左子樹是2*i+1,右子樹是2*i+2。
三、PriorityQueue佇列的存取原理
1、首先看下原始碼
/** * Inserts item x at position k, maintaining heap invariant by * promoting x up the tree until it is greater than or equal to * its parent, or is the root. * * To simplify and speed up coercions and comparisons. the * Comparable and Comparator versions are separated into different * methods that are otherwise identical. (Similarly for siftDown.) * * @param k the position to fill * @param x the item to insert */ private void siftUp(int k, E x) { if (comparator != null) siftUpUsingComparator(k, x); else siftUpComparable(k, x); }
在呼叫offer(E e)方法入隊時,首先進行非null判斷,然後是grow方法擴容,緊接著就是siftUp方法調整節點位置,那麼是如何調整的呢?為什麼要調整?
2、為什麼要調整節點順序?
因為這是一個最小堆,最小堆必須要嚴格按照子節點大於父親節點的順序做陣列中存放。
3、如何調整?
siftup方法有個if-else判斷,如果有比較器,則使用siftUpUsingComparator(k, x);如果沒有則呼叫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;
}
4、如何出隊
public E poll() {
if (size == 0)
return null;
int s = --size;
modCount++;
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x);
return result;
}
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;
}
出隊和入隊原理正好相反,每次出隊也要進行siftDown操作,對元素順序重新整理。四、總結
PriorityQueue佇列不適合進場出隊入隊的頻繁操作,但是他的優先順序特性非常適合一些對順序有要求的資料處理場合。