黃明志
(仲愷農業工程學院信息科學與技術學院,廣州 510225)
優先級隊列類(PriorityQueue
二叉堆是一棵完全二叉樹(Complete Binary Tree),對于每個父節點,它的值均小于等于(或大于等于)其子節點。而對于具有n個節點的完全二叉樹,可以按照從上到下(從根節點到葉節點)、從左到右的規則將各個節點映射到一個大小為n的一維數組中。
最小堆是指父節點的值均小于或等于其子節點的堆。而最大堆則是指父節點的值均大于或等于其子節點的堆。
二叉堆具有的特性:一是根節點的優先級最高;二是當新增節點或移除根節點后能夠快速地重新建堆。因此,使用堆來存放元素是非常適合優先級隊列這種數據結構的。
計算某個節點的子節點在數組中基于0的索引的方法如下:對于某個索引為i的節點,左子節點的索引為2*i+1;而右子節點的索引則為2*(i+1),當然,也可以表示為左節點的索引加1。
堆化是指將數組變為最小堆(或最大堆)數組的操作。對于存儲有n個節點的堆數組,堆化操作依次地從n/2-1開始,直到編號為0的節點。如果要將整個堆堆化為最小堆,則每次操作就是要比較當前節點與其子節點的值,確保當前節點的值比子節點的要小;否則,就將當前節點與其子節點進行交換,使其滿足最小堆的性質。例如,假設數組共有9個元素,初始時處于無序狀態(如圖1);堆化操作將從索引為3(計算方法為:9/2-1,取整后為3)的節點開始,通過比較,發現其右子節點的值較小,故需對調其位置(如圖2);同理,當堆化索引為1的節點時,首先是值為1和4的兩個節點的位置對調(箭頭1,如圖3),然后是值為4和3的兩個節點的位置也對調(箭頭2);整棵二叉樹完全堆化后如圖4所示。

圖1 初始時的無序狀態

圖2 堆化索引為3的節點

圖3 堆化索引為1的節點

圖4 堆化完成后的狀態
PriorityQueue
類的開始處的程序代碼如下:



圖5 PriorityQueue
基于性能的考慮,在PriorityQueue
Comparer用于確定元素的優先級的大小。其值可通過構造函數傳遞過來,如果從構造函數中傳遞過來的比較器為null,則使用相應類型比較器的默認值(即Comparer
modified標志供迭代時若發現隊列中有任何元素被更改時作適當的處理。
PriorityQueue

在實例化對象時,如果collection參數為非null,則需要進行堆化操作。下述的Heapify方法建立最小堆:

對于最小堆來說,就像上述基本算法所介紹的那樣,SiftDown的作用是將一個節點和它的子節點進行比較,保證它比其所有的子節點都要小,否則,就通過交換兩個節點來滿足這個要求。這個調整或交換的順序是從當前節點向下,一直到葉節點為止:

元素入隊時,若發現數組已滿,則將數組的容量擴大一倍的操作由Array.Resize實施。

其中,SiftUp方法是將入隊元素調整到恰當的位置,以確保整個堆仍然處于最小堆的狀態。


入隊的元素可以為null,這樣的設計考慮使得PriorityQueue
如果不考慮自動擴展數組容量的操作,入隊操作的時間復雜度主要由SiftUp方法來決定,即其時間復雜度為 O(logN)。
由于堆處于最小堆狀態,因此,出隊的操作就是返回數組中的第一個元素。當然,返回并移除優先級最高的元素時,需要進行必要的調整,以確保堆再次處于最小堆的狀態。另外,當隊列為空(無元素)時拋出InvalidOperationException異常(這與.NET中Queue

出隊操作的時間復雜度主要由SiftDown方法來決定,出隊操作的時間復雜度O(logN)。
顯而易見,Peek操作無需調用SiftDown方法,其時間復雜度為 O(1)。
為了實現在泛型的優先級隊列中進行簡單迭代操作,同時讓優先級隊列類的設計具有良好的封裝性,在PriorityQueue
(1)MoveNext方法
Enumerator

(2)Current屬性
通過Current屬性,獲取集合中的當前元素。public E Current

(3)獲取枚舉數并實現IEnumerable

通過上述的設計,就令PriorityQueue
實現IEnumerable
(1)實現接口中的基本成員
ICollection接口主要有CopyTo和GetEnumerator方法以及Count、IsSynchronized和SyncRoot屬性。例如,獲取可用于同步ICollection訪問的對象的代碼為“public Object SyncRoot{get{return heap;}}”;而 IsSynchronized屬性總是返回false。
(2)實現顯式接口
優先級隊列類實現了若干個顯式接口。例如,獲取一個循環訪問集合的枚舉數就有IEnumerable.GetE-numerator和IEnumerable

而顯式接口ICollection.CopyTo的實現則如下:void ICollection.CopyTo(Array array,int arrayIndex)

而公共的CopyTo是通過顯式將this對象轉換為ICollection接口后調用上述顯式方法實現的,也就是說,其方法體中僅需“((ICollection)this).CopyTo(array,arrarIndex)”這樣一行語句即可。
為了更簡單起見,上述代碼中實例化各個異常時所傳入的用于描述異常信息的字符串僅以參數名表示,而實際上應為可更明確地描述異常的文本。
對于實現IComparerable
如果要按從大到小的順序出隊,就需要設計一個比較器,將比較操作反轉。下面設計一個將優先級反轉的類,以簡化這類常見操作的使用:

這樣,在構造上述整型優先級隊列時,使用以下的比較器實例作為實參來構造隊列即可實現將出隊順序由使用默認比較器時的從小到大反轉為從大到小了:Comparer
當元素類并沒有實現IComparerable
優先級隊列在寬帶管理等應用場景中被廣泛使用,而.NET中并沒有相應的類。因PriorityQueue