Java資料結構與演算法解析(十三)——優先順序隊列
在很多應用中,我們通常需要按照優先順序情況對待處理對象進行處理,比如首先處理優先順序最高的對象,然後處理次高的對象。最簡單的一個例子就是,在手機上玩遊戲的時候,如果有來電,那麼系統應該優先處理打進來的電話。
在這種情況下,我們的資料結構應該提供兩個最基本的操作,一個是返回最高優先順序對象,一個是添加新的對象。這種資料結構就是優先順序隊列(Priority Queue) 。 定義
優先順序隊列和通常的棧和隊列一樣,只不過裡面的每一個元素都有一個”優先順序”,在處理的時候,首先處理優先順序最高的。如果兩個元素具有相同的優先順序,則按照他們插入到隊列中的先後順序處理。
優先順序隊列可以通過鏈表,數組,堆或者其他資料結構實現。
優先順序隊列的實現方式 數組
最簡單的優先順序隊列可以通過有序或者無序數組來實現,當要擷取最大值的時候,對數組進行尋找返回即可。
無序數組實現
如果使用無序數組,那麼每一次插入的時候,直接在數組末尾插入即可,時間複雜度為O(1),但是如果要擷取最大值,或者最小值返回的話,則需要進行尋找,這時時間複雜度為O(n)。
要實現刪除最大元素,可以添加一段類似於選擇排序的內迴圈代碼,將最大元素的和邊界元素交換後刪除它,和對棧的pop()方法的實現一樣。同樣也可以加入調整數組的代碼來達到動態調整數組的目的。
有序數組實現
如果使用有序數組,那麼每一次插入的時候,通過插入排序將元素放到正確的位置,時間複雜度為O(n),但是如果要擷取最大值的話,由於元阿蘇已經有序,直接返回數組末尾的 元素即可,所以時間複雜度為O(1).
在insert方法中添加代碼,將所有較大的元素向右邊移動一格以使數組保持有序(和插入排序一樣)。這樣,最大的元素總會在數組的一邊,刪除最大元素,只需要像棧的pop()一樣就可以了。。
所以採用普通的數組或者鏈表實現,無法使得插入和排序都達到比較好的時間複雜度。所以我們需要二元堆積(binary heap)來實現優先順序隊列 鏈表標記法
我們還可以使用基於鏈表的下壓棧的代碼作為基礎,而後可以選擇修改pop()來找到並返回最大元素,或是修改push()來保證所有元素的逆序並用pop()來刪除並返回鏈表的首元素。
二元堆積
二元堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的索引值或索引總是小於(或者大於)它的父節點。 有了這一性質,那麼二元堆積上最大值就是根節點了。
二元堆積的表現形式:我們可以使用數組的索引來表示元素在二元堆積中的位置。
從二元堆積中,我們可以得出:
· 元素k的父節點所在的位置為[k/2]
· 元素k的子節點所在的位置為2k和2k+1
跟據以上規則,我們可以使用二維數組的索引來表示二元堆積。通過二元堆積,我們可以實現插入和刪除最大值都達到O(nlogn)的時間複雜度。
對於堆來說,最大元素已經位於根節點,那麼刪除操作就是移除並返回根節點元素,這時候二元堆積就需要重新排列;當插入新的元素的時候,也需要重新排列二元堆積以滿足二元堆積的定義。
從下至上的堆有序變化
如果一個節點的值大於其父節點的值,那麼該節點就需要上移,一直到滿足該節點大於其兩個子節點,而小於其根節點為止,從而達到使整個堆實現二元堆積的要求。
我們只需要將該元素k和其父元素k/2進行比較,如果比父元素大,則交換,然後迭代,一直到比父元素小為止。
private static void Swim(int k){ //如果元素比其父元素大,則交換 while (k > 1 && pq[k].CompareTo(pq[k / 2]) > 0) { Swap(pq, k, k / 2); k = k / 2; }}
1 2 3 4 5 6 7 8 9
這樣,往堆中插入新元素的操作變成了,將該元素從下往上重建立堆操作:
代碼實現如下:
public static void Insert(T s){ //將元素添加到數組末尾 pq[++N] = s; //然後讓該元素從下至上重建堆 Swim(N);}
1 2 3 4 5 6 7
由上至下的堆有序變化
當某一節點比其子節點要小的時候,就違反了二元堆積的定義,需要和其子節點進行交換以重建立堆,直到該節點都大於其子節點為止:
private static void Sink(int k){ while (2 * k < N) { int j = 2 * k; //去左右子節點中,稍大的那個元素做比較 if (pq[j].CompareTo(pq[j + 1]) < 0) j++; //如果父節點比這個較大的元素還大,表示滿足要求,退出 if (pq[k].CompareTo(pq[j]) > 0) break; //否則,與子節點進行交換 Swap(pq, k, j); k = j; }}
1 2 3 4 5 6 7 8 9 10 11 12 13 14
這樣,移除並返回最大元素操作DelMax可以變為:
移除二元堆積根節點元素,並返回
將數組中最後一個元素放到根節點位置
然後對新的根節點元素進行Sink操作,直到滿足二元堆積要求。
移除最大值並返回的操作如下圖所示:
public static T DelMax(){ //根項目從1開始,0不存放值 T max = pq[1]; //將最後一個元素和根節點元素進行交換 Swap(pq, 1, N--); //對根節點從上至下重建立堆 Sink(1); //將最後一個元素置為空白 pq[N + 1] = default(T); return max;}
1 2 3 4 5 6 7 8 9 10 11 12
多叉堆
基於用數組表示的完全三叉樹構造堆並修改相應的代碼並不難,對應數組中1至N的N個元素,位置k的結點大於大於等於3k-1,3k,3k+1的結點,小於位於[(k+1)/3]下取整的結點。 d叉堆
完全d叉樹,根最小。儲存時使用層序。
操作跟二元堆積基本一致:insert,deleteMin,增大元素,減小元素,刪除非頂元素,merge。
二元堆積與d叉堆的對比: