JAVA隊列之優先隊列__JAVA

來源:互聯網
上載者:User

最近在項目開發中開發了全雙工系統非同步長串連的通訊群組件,內部用到了延遲隊列。而延遲隊列的內部實現的儲存是用到了優先隊列,當時看C++的資料結構時,瞭解過優先隊列,用的儲存是二叉樹的邏輯,應該叫完全二叉樹,也可以叫做最大堆。

下面看一下二叉樹的演算法,主要看插入和刪除。

二叉樹顧名思義就像一棵樹,每個節點下最多可以掛兩個節點,如圖


在優先隊列中儲存的方式就是 queue = {A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q};

若當前節點下標為i   那麼它的子節點  左側 = 2*i+1  右側 = 2*i +2。優先隊列顧名思義,就是優先權最大的排在隊列的頭部,而優先權的判斷是根據對象的compare方法比較擷取的,保證根節點的優先順序一定比子節點的優先順序大。所以放入到優先隊列的元素要麼實現了Comparable介面,要麼在創造這個優先隊列時,指定一個比較子。

下面我們來看一下優先隊列的建構函式

private static final int DEFAULT_INITIAL_CAPACITY = 11;    private final Comparator<? super E> comparator;    public PriorityQueue() {            this(DEFAULT_INITIAL_CAPACITY, null);        }    public PriorityQueue(int initialCapacity,                             Comparator<? super E> comparator) {            // Note: This restriction of at least one is not actually needed,            // but continues for 1.5 compatibility            if (initialCapacity < 1)                throw new IllegalArgumentException();            this.queue = new Object[initialCapacity];            this.comparator = comparator;        }


 

 在空構造器初始化時,數組隊列的大小為11,比較子為null。這樣的話,放入優先隊列的對象要實現comparable介面,也可以判斷隊列中不能存放null元素。 

現在我們來看一下往隊列中添加一個對象的過程。

public boolean add(E e) {        return offer(e);    }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;    }

可以看到,優先隊列中不可能存在null元素,添加元素首先把隊列修改次數++,判斷是否超過內部數組的長度,超過後增加數組的長度 grow(i+1)

private void grow(int minCapacity) {        if (minCapacity < 0) // overflow            throw new OutOfMemoryError();int oldCapacity = queue.length;        // Double size if small; else grow by 50%        int newCapacity = ((oldCapacity < 64)?                           ((oldCapacity + 1) * 2):                           ((oldCapacity / 2) * 3));        if (newCapacity < 0) // overflow            newCapacity = Integer.MAX_VALUE;        if (newCapacity < minCapacity)            newCapacity = minCapacity;        queue = Arrays.copyOf(queue, newCapacity);    }
如果數組長度小於64  是按照一倍長度增長的,長度超過64,每次增長原來的百分之50,如果長度超過了int的最大值,那就設定為
Integer.MAX_VALUE,如果新的長度小於需要增長的長度,就是設定成這個長度,最後複製原來的對象到新的數組。


這樣數組長度擴充了,設定size+1。如果是空的優先隊列,就把新元素添加到隊列頭部。如果不是空,那就根據compareTo來判斷新加入的元素的優先順序別,那麼我們來看一下siftUp方法

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 = parent;        }        queue[k] = key;    }    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;    }

如果在建立隊列的時候,指定了比較工具,那麼就用比較子,比較子內部實現,可以根據自己的定義的比較原則,也可以調用排入佇列的元素的compareTo方法(如果實現的話),比較方法變化自行定義。

我們主要看一下沒有比較子的排序方法siftUpComparable,如下圖


假設數字越小,優先順序別越高,K的位置是新插入元素將要放置的位置,加入插入的是 25,那麼此元素的優先順序別小於它的父節點的優先順序,直接把25放在K的位置。假如放置的是8,優先順序別最高,所以 (k-1)>>>1 擷取父節點,與父節點比較,發現優先順序大於父節點,把父節點的值放入K, 把K的位置設定成父節點的下標值,遞迴查詢父節點,定位出K的位置,把新元素放入此位置。此種二叉樹演算法大大降低了演算法複雜度。


接下來我們看一下取走隊列頭部元素

 
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 siftDown(int k, E x) {        if (comparator != null)            siftDownUsingComparator(k, x);        else            siftDownComparable(k, x);    }    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;    }    private void siftDownUsingComparator(int k, E x) {        int half = size >>> 1;        while (k < half) {            int child = (k << 1) + 1;            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的位置為null。如果不是一個元素,彈出第一個元素,擷取數組最後的一個元素,開始調用siftDown(0, x);我們先來看一下圖解


這樣到poll掉隊列頭部元素後,隊列長度減一,所以最後的那個元素32的位置需要設定成null,這樣一來,需要填補隊列的頭,由於子節點一定小於等於父節點的優先順序,擷取父節點的子節點

int child = (k << 1) + 1;    // 擷取左子節點的下標
Object c = queue[child];  // 擷取左子節點
int right = child + 1;          // 擷取右子節點的下標

但右子節點不一定存在所以下面做了一個右節點不會為null的判斷,不為null的情況下,擷取兩個節點中優先順序高的一個

if (right < size &&((Comparable<? super E>) c).compareTo((E) queue[right]) > 0){

  c = queue[child = right];

}

//而下面這段代碼的意思是可能出現如下圖的情況

if (key.compareTo((E) c) <= 0){

break;

}


                
              當21移到隊列頭部時,隊列尾部的 32元素的優先順序 大於68和67的優先順序,可以直接把32元素放入到21的位置,直接結束迴圈,減少了下面空出元素的位置的重新排序演算法。

用while(k<half)  因為k如果>=half 的話,此下標以下的節點無子節點,完全二叉樹定義,也可以推到。

 優先隊列 也就這兩個比較重要的方法,對於remove(Object o)方法,無非是刪除中間節點後的一種先向下排序,再向上排序的過程,可以自行查看。


歡迎交流。

























聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.