1. 程式人生 > >PriorityQueue優先佇列實現原理

PriorityQueue優先佇列實現原理

一、什麼是優先佇列

   優先佇列不是按照普通物件先進先出原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佇列不適合進場出隊入隊的頻繁操作,但是他的優先順序特性非常適合一些對順序有要求的資料處理場合。