【java源碼一帶一路系列】之PriorityQueue

按照下圖的配方,走了一遍源碼。
湊齊PriorityQueue就能夠召喚神龍了。
Ler's go go go!java

clipboard.png
clipboard.png

結構

/**
 * Priority queue represented as a balanced binary heap: the two
 * children of queue[n] are queue[2*n+1] and queue[2*(n+1)].  The
 * priority queue is ordered by comparator, or by the elements'
 * natural ordering, if comparator is null: For each node n in the
 * heap and each descendant d of n, n <= d.  The element with the
 * lowest value is in queue[0], assuming the queue is nonempty.
 */
transient Object[] queue; // non-private to simplify nested class access

沒錯這是個數組,爲了更好的理解註釋的含義,請看下面↓。node

滿二叉樹:git

全部的節點都有2個葉子節點,除了最後層葉子節點;github

節點數n和深度d的關係 n=2^d-1;api

第i層上的節點數爲2^(i-1);數組

第n個節點的父節點:n/2,左子節點:2n,右子節點:2n+1;(參考下圖)安全

滿二叉樹與徹底二叉樹

徹底二叉樹:markdown

有且僅有最底層葉子節點不完整就是徹底二叉樹。(例如:把15去掉)框架

最小堆:less

父節點小於左右子節點的徹底二叉樹。

轉數組:

用數組來存儲二叉樹後(參見下圖)可得,根節點A[0];左子節點a[2n+1];右子節點a[2(n+1)],父節點a[(n-1)/2]。(n爲數組下標,從0開始)

二叉樹轉數組

是的,優先隊列的存儲結構大概就是這樣推演而來。

heapify() --> siftDown() --> siftDownComparable() --> siftDownUsingComparator()

/**
 * Establishes the heap invariant (described above) in the entire tree,
 * assuming nothing about the order of the elements prior to the call.
 */
@SuppressWarnings("unchecked")
private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]);
}

/**
 * Inserts item x at position k, maintaining heap invariant by
 * demoting x down the tree repeatedly until it is less than or
 * equal to its children or is a leaf.
 *
 * @param k the position to fill
 * @param x the item to insert
 */
private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

@SuppressWarnings("unchecked")
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;
}

@SuppressWarnings("unchecked")
private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1; //2n + 1,這裏n是下標
        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;
}

這是任意數組最小堆化的過程。若是是一個合格的最小堆,那麼全部的父節點都在數組前半部分,而經過父節點又能獲得左右子節點。所以源碼一上來就「size >>> 1」(至關於除以2),只需對前半部分進行循環處理,使得循環結束後全部父節點均大於左/右子節點。這裏非根父節點會被屢次比較到。heapify()後將獲得上文所說的最小堆數組。

@SuppressWarnings("unchecked")
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;
}

poll()的核心也是siftDown,而這裏的「siftDown(0, x);」與以前的「siftDown(i, (E) queue[i]);」不一樣的是,下標0所對應的元素本非x。也就是說,這裏進行了個轉換:把最後queue[s]替換了queue[0]進行新的最小堆數組化。

/**
 * Removes a single instance of the specified element from this queue,
 * if it is present.  More formally, removes an element {@code e} such
 * that {@code o.equals(e)}, if this queue contains one or more such
 * elements.  Returns {@code true} if and only if this queue contained
 * the specified element (or equivalently, if this queue changed as a
 * result of the call).
 *
 * @param o element to be removed from this queue, if present
 * @return {@code true} if this queue changed as a result of the call
 */
public boolean remove(Object o) {
    int i = indexOf(o);
    if (i == -1)
        return false;
    else {
        removeAt(i);
        return true;
    }
}

/**
 * Removes the ith element from queue.
 *
 * Normally this method leaves the elements at up to i-1,
 * inclusive, untouched.  Under these circumstances, it returns
 * null.  Occasionally, in order to maintain the heap invariant,
 * it must swap a later element of the list with one earlier than
 * i.  Under these circumstances, this method returns the element
 * that was previously at the end of the list and is now at some
 * position before i. This fact is used by iterator.remove so as to
 * avoid missing traversing elements.
 */
@SuppressWarnings("unchecked")
private E removeAt(int i) {
    // assert i >= 0 && i < size;
    modCount++;
    int s = --size;
    if (s == i) // removed last element
        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;
}

如你所見,remove()也用到了siftDown()(同時還有siftUp(),下面介紹)。這裏 通過siftDown後,若是queue[i] == moved則表示queue[i]的左右子節點都大於moved,即保證了i節點子樹是最小堆,但queue[i]的父節點是否小於moved卻未知,故又進行了siftUp。(圖片來自【2】)

image

/**
 * 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);
}
    
@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;
}

@SuppressWarnings("unchecked")
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;
}

與之相對的,還有名爲siftUpComparable()/siftUpUsingComparator()的方法。在新增元素時被調用。新增元素放在下標爲size的位置。這裏的down與up指的是被比較對象x的去向。比較後x被賦值給子節點就是down,被賦值給父節點就是up。固然你來寫的時候也可能新增時,從上到下循環遍歷。

說點什麼

PriorityQueue有序;不容許爲null;非線程安全;(PriorityBlockingQueue線程安全);沒有介紹的地方大抵與其餘集合框架類似,如擴容機制等。

優先隊列每次出隊的元素都是優先級最高(權值最小)的元素,經過比較(Comparator或元素自己天然排序)決定優先級。

記得常來複習啊~~~

更多有意思的內容,歡迎訪問筆者小站: rebey.cn

推薦文章:

【1】【深刻理解Java集合框架】深刻理解Java PriorityQueue;

【2】java集合——Queue;

相關文章
相關標籤/搜索