第六章:優先隊列(堆)
[TOC]算法
思考以下場景,老師佈置了不少做業,如今你須要將做業打印出來,你將做業文件依照隊列的形式放入待打印列表中,但此時,你但願最重要(或者是立刻就要上交)的做業優先打印出來.此時,隊列結構顯然不能知足咱們的需求,這時候咱們考慮一種名爲優先隊列(priority queue)的數據結構,或者稱之爲堆.數組
6.1 模型數據結構
Insert(插入):等價於隊列中 Enqueue(入隊).ide
6.2 一些簡單的實現函數
使用一個簡單鏈表再表頭以 $ O(1) $ 執行插入操做,並遍歷該鏈表以刪除最小元,這須要 $ O(N) $ 的時間.測試
另外一種方法,始終讓表表示排序轉檯,這會使得插入操做花費 $ O(N) $ 時間,而 DeleteMin 操做花費 $ O(1) $ 時間.*考慮到 DeleteMin 操做並很少於 Insert 操做,所以在這兩者之間,第一種方法可能更好.ui
6.3 二叉堆spa
6.3.1 結構性質3d
一棵高度爲 $ h $ 的徹底二叉樹有 $ 2^h $ 到 $ 2^{h+1} - 1 $ 個節點,這意味着,對於擁有 $ N $ 個節點的徹底二叉樹而言,它的高度爲 $ \lfloor logN \rfloor $ ,顯然爲 $ O(logN) $.
十分重要的一點,因爲徹底二叉樹具備規律性,因此它能夠用數組來表示.能夠代替指針.以下.指針
對於數組中任意一個位置 $ i $ 上的元素,其左兒子在位置 $ 2i $ 上,右兒子在位置 $ 2i + 1 $ 上,它們的父親在位 置$ \lfloor \frac{i}{2} \rfloor $ 上.
用數組表示的優勢再也不贅述,但有一個缺點須要提醒,使用數組表示二叉堆須要實現估計堆的大小,但這對於典型狀況不成問題.
//優先隊列的聲明 struct HeapStruct; typedef struct HeapStruct *PriorityQueue; struct HeapStruct{ int Capacity; int Size; ElementType *Elements; }; PriorityQueue Initialize( int MaxElements); void Destroy( PriorityQueue H); void MakeEmpty( PriorityQueue H); void Insert( ElementType X, PriorityQueue H); ElementType DeleteMin( PriorityQueue H); ElementType FindMin( PriorityQueue H); int IsEmpty( PriorityQueue H); int IsFull( PriorityQueue H); PriorityQueue Initialize( int MaxElements){ PriorityQueue H; if( MaxElements < MinPQSize) Error(" Priority queue size is too small"); H = malloc( sizeof( struct HeapStruct)); if( H = NULL) FatalError(" Out of space"); H->Elements = malloc( ( MaxElements + 1) * sizeof( ElementType)); if( H->Elements == NULL) FatalError(" Out of space"); H->Capacity = MaxElements; H->Size = 0; H->Elements[0] = MinData;//標記 return H; }
6.3.2 堆序性質
堆序(heap order):使操做被快速執行的性質,依據堆的性質,最小元老是能夠在根處找到.
Insert 插入 考慮將一個元素 $ X $ 插入堆中,咱們將在下一個位置創建一個空穴,不然該堆將再也不是徹底二叉樹.
若是 $ X $ 能夠放到該空穴中而不影響堆的序,那麼插入操做完成.
void Insert( ElementType X, PriorityQueue H){ int i; if( IsFull( H)){ Error(" Priority queue is full"); return ; } for( i = ++H->Size; H->Elements[i/2] > X; i/=2) H->Elements[i] = H->Elements[i/2]; H->Elements[ i] = X; }
若是插入的元素是新的最小值,那麼它將被推到頂端.在頂端, $ i = 1 $ ,咱們能夠跳出循環,能夠在 $ i = 0 $ 處放置一個很小的值使得循環停止.這個值咱們稱之爲標記(sentinel),相似於鏈表中頭節點的使用,經過添加一條啞信息(dummy piece of infomation),避免每次循環都要執行一次測試,從而節省了一點時間.
若是插入的元素是新的最小元,已知過濾到根部,那麼這種插入的時間複雜度顯然是 $ O(logN) $ .
若是 $ X $ 能夠移動到空穴中去,那麼 DeleteMin 操做完成.
其中須要注意的一點是,當堆中存在偶數個元素時,此時將會遇到一個節點只有一個兒子的狀況,咱們沒必要須保證節點不總有兩個兒子,所以這就涉及到一個附加節點的測試.
ElementType DeleteMin( PriorityQueue H){ int i, Child; ElementType MinElement, LastElement; if( IsEmpty( H)){ Error(" Priority queue is empty"); return H->Elements[0]; } MinElement = H->Elements[1]; LastElement = H->Elements[ H->Size--]; for( i=1; i*2 <= H->Size; i = Child){ Child = i * 2; if( Child != H->Size && H->Elements[ Child + 1] < H->Elements[ Child]) Child++; //if語句用來測試堆中含有偶數個元素的狀況 if( LastElement > H->Elements[ Child]) H->Elements[i] = H->Elements[ Child]; else break; } H->Elements[i] = LastElement; return MinElement; }
這種算法的最壞運行時間爲 $ O(logN) $ ,平均而言,被放到根除的元素幾乎下濾到堆的底層.
6.3.4 其餘堆的操做
對於最小堆(min heap)而言,求最小元易如反掌,可是求最大元,卻不得不採起遍歷的手段.對於最大堆找最小元亦是如此.
咱們看到,關於最大值的元素所知道的惟一信息是:該元素位於樹葉上.可是,有近半數的元素位於樹葉上,所以該信息是沒有什麼用處的.出於這個緣由,若是咱們要知道某個元素處在什麼位置上,除了堆以外,還要用到諸如散列表等某些其餘的數據結構.
若是咱們假設經過某種其餘方法得知每個元素的位置,那麼有幾種其餘的操做的開銷將變小.
下列三種這樣的操做均以對數最壞情形時間運行.
DecreaseKey 下降關鍵字的值
DecreaseKey(P, $ \Delta $ , H)操做將下降在位置 P 處關鍵字的值,下降的幅度爲 $ \Delta $( $ \Delta > 0 $ ),因爲這可能會破壞堆的序,所以必須經過上濾對堆進行調整.該操做堆系統管理程序是游泳的,系統管理程序可以使它們的程序以最高的優先級來運行.
IncreaseKey 增長關鍵字的值
IncreaseKey(P, $ \Delta $ , H)操做將增長在位置 P 處關鍵字的值,增值的幅度爲 $ \Delta $( $ \Delta > 0 $ ).能夠經過下濾來完成.許多調度程序自動地下降正在過多小號CPU時間的進程的優先級.
Delete 刪除
Delete(P, H)操做刪除堆中位置 P 上的節點.這經過首先執行DecreaseKey(P, $ \infty $ , H),而後再執行 DeleteMin(H).當一個進程被用戶停止(非正常停止)時,它不準從優先隊列中除去.
for( i = N/2; i>0; i--)
PercolateDown(i);
以下例    爲了肯定 BuildHeap 的運行時間的界,咱們肯定虛線條數的界,這能夠經過計算樹中全部節點的高度和來獲得,它是虛線的最大條數,如今咱們說明,該和爲$ O(N) $ * 定理:包含 $ 2^{b+1} - 1 $ 個節點高爲 $ b $ 的理想二叉樹(perfect binary tree)的節點的高度的和爲 $ 2^{b+1} - 1 - ( b + 1 ) $ 證實:容易看出如下規律:  則全部節點的高度和 $ S = 2^{0} * b + 2^{1} *( b - 1 ) + ... + 2^{b-1} * 1+ 2^{b} * 0 = $ 利用裂項相消,獲得 $ S = 2^{b+1} - 1 - ( b + 1 ) $ **6.4 優先隊列的應用** **6.4.1 選擇問題** 選擇問題(selection problem):從一組 N 個數而要肯定其中第 k 個最大者. * 算法一: 把這些元素依次讀入數組並給他們排序,同時返回適當的元素.該算法的時間複雜度爲 $ O(N^2) $ * 算法二:先把前 k 個元素讀入數組並按照遞減的順序排序,以後,將剩下的元素逐個讀入,當新元素讀入時,若是它小於數組中的第 k 個元素則忽略,不然就將它放到數組中正確的位置上.同時將數組中的一個元素擠出數組,當算法停止的時候,第 k 個位置上的元素做爲關鍵字返回便可.該算法的時間複雜度是 $ O(kN) $,當 $ k = \lceil \frac{N}{2} \rceil $ 時,該算法時間複雜度爲 $ O(N^2) $ .注意,對於任意的 k ,咱們能夠求解對稱的問題:找出第 (N - k + 1) 個最小的元素,從而 $ k = \lceil \frac{N}{2} \rceil $ 實際上時這兩種算法最困難的狀況,此時 k 處的值被稱爲中位數(median). * 算法三:爲簡單起見,假設咱們只考慮找出第 k 個最小的元素.咱們將 N 個元素讀入一個數組,而後堆數組應用 BuildHeap 算法,最後,執行 k 次 DeleteMin 操做.從該堆中最後提取的元素就是咱們須要的答案.顯然,改變堆序的性質,咱們能夠求原式問題:找出第 k 個最大的元素. 算法的正確性顯然. 考慮算法的時間複雜度,若是使用 BuildHeap ,構造堆的最壞情形用時爲 $ O(N) $ ,而每次 DeleteMin 用時 $ O(logN) $ .因爲有 k 次 DeleteMin,所以咱們獲得總的運行時間爲 $ O(N + klogN) $ . 1. 若是 $ k = O(\frac{N}{logN} ) $ ,那麼運行時間取決於 BuildHeap 操做,即O(N). 1. 對於大的 k 值,運行時間爲 $ O(klogN) $. 1. 若是 $ k = \lceil \frac{N}{2} \rceil $,那麼運行時間爲 $ \Theta(NlogN) $ * 算法四:回到原始問題:找出第 k 個最大的元素.在任意時刻咱們都將維持 k 個最大元素的集合 S 。在前 k 個元素讀入以後,當再讀入一個新的元素時,該元素將與第 k 個最大元素進行比較,記這第 k 個最大的元素爲 $ Sk $ .注意, $ Sk $ 是集合 S 中最小的一個元素.若是新元素比 $ Sk $ 大,那麼用新元素替代集合 S 中的 $ Sk $ .此時,集合 S 中將有一個新的最小元素,它與偶多是剛剛添加進的元素,也有可能不是.當輸入終止時,咱們找到集合 S 中的最小元素,並將其返回,這邊是答案. 算法四思想與算法二相同.不過,在算法四中,咱們用一個堆來實現集合 S ,前 k 個元素經過調用依次 BuildHeap 以總時間 $ O(k) $ 被置入堆中.處理每一個其他的元素花費的時間爲 $ O(1) + O(logk) $,其中 $ O(1) $ 部分是檢查元素是否進入 S 中, O(logk)部分是再必要時刪除 $ S_k $ 並插入新元素.所以總時間是 $ O(k + ( N - k )logk) = O( Nlogk) $.若是 $ k = \lceil \frac{N}{2} \rceil $,那麼運行時間爲 $ \Theta(NlogN) $. **6.4.2 時間模擬** * 咱們假設有一個系統,好比銀行,顧客們到達並站隊等在哪裏直到 k 個出納員中有一個騰出手來.咱們關注在於一個顧客平均要等多久或隊伍可能有多長這樣的統計問題. * 對於某些機率分佈以及 k 的取值,答案均可以精確地計算出來.然而伴隨着 k 逐漸變大,分析明顯地變得困難. * 模擬由處理中的時間組成.這裏有兩個事件. * 一個顧客的到達. * 一個顧客的離開,從而騰出一名出納員. * 咱們可使用機率函數來生成一個輸入流,它由每一個顧客的到達時間和服務時間的序偶組成,並經過到達時間排序.咱們沒必要使用一天中的精準時間,而是經過名爲滴答(tick)的單位時間量. 進行這種模擬的一種方法是在啓動處在 0 滴答處到額一臺摸擬鐘錶.讓鐘錶一次走一個滴答,同時查詢是否有一個事件發生.若是有,那麼咱們處理事件,蒐集統計資料.當沒有顧客留在輸入流中且全部出納員都閒置,模擬結束. 可是這種模擬問題,它運行的時間不依賴顧客數量或者時間數量,而是依賴於滴答數,可是滴答數又不是實際輸入.不妨假設將時鐘的單位改爲滴答的千分之一並將輸入中的全部時間乘以 1000 ,那麼結果即是,模擬用時增長了1000倍. 避免這種問題的關鍵在於在每個階段讓重表直接走到下一個事件時間,從概念上來看這是容易作到的.在任一時刻,可能出現的下一個時間或者是下一個顧客到達,或者是有一個顧客離開,同時一個出納員閒置.因爲能夠得知將發生事件的全部時間,所以咱們只須要找出最近的要發生的事件並處理這個事件. * 若是這個事件是有顧客離開,那麼處理過程包括蒐集離開的顧客的統計資料及檢驗隊列看看是否還有其餘顧客在等待.若是有,那麼咱們加上這位顧客,處理所須要的統計資料,計算該顧客將要離開的時間,並將離開事件假到等待發生的事件集中區. * 若是事件是有顧客到達,那麼咱們檢查閒置的出納員.若是沒有,那麼咱們把該到達時間放置到隊列中去;不然,咱們分配顧客一個出納員,計算顧客離開的時間,並將離開事件加到等待發生的事件集中區. 在等待的顧客隊伍能夠實現爲一個隊列.因爲咱們須要找到最近的將要發生的事件,合適的辦法是將等待發生的離開的結合編入到一個優先隊列中.下一個事件是下一個到達或者下一個離開. * 考慮以上算法的時間複雜度,若是有 C 個顧客(所以有 2C 個事件 )和 k 個出納員,那麼模擬的運行時間將會是 $ O( Clog(k+1)) $ ,由於計算和處理每一個事件花費時間爲 $ O(logH) $ ,其中 $ H = k + 1 $ 爲堆的大小. **6.5 d-堆** * d-堆是二叉堆的推廣,它像一個二叉堆,可是全部的節點都有 d 個兒子(注意,二叉堆是一個2-堆).  如上圖所示,3-堆。 **6.6 左式堆** * Merge 合併操做,堆而言,合併操做是最困難的操做。 * 考慮到堆結構沒法用數組實現以 $ O(N) $ 高效的合併操做。所以,全部支持高效合併的高級數據結構都須要使用指針。 * 左式堆(leftist heap):它與二叉樹之間的惟一區別是,左式堆不是理想平衡的,而其實是趨向於很是不平衡。它具備相同的堆序性質,若有序性和結構特性。 **6.6.1 左式堆的性質** * 零路徑長(null path length, NPL):從任一節點 $ X $ 到一個沒有兩個兒子的節點的最短路徑長。所以,具備 0 個或 1 個兒子的節點的 Npl 爲 0,而 Npl(NULL) = -1. 注意,任一節點的零路徑長比它諸兒子節點的零路徑長的最小值多 1 。這個結論也適用少於兩個兒子的節點,由於 NULL 的零路徑長爲 -1 . * 性質:對於左式堆中的每個節點 $ X $ ,左則日子的零路徑長至少與右兒子的零路徑長同樣大。以下圖所示:  在圖示中,右邊的樹並非左式堆,由於他不知足左式堆的性質。 左式堆的性質顯然更加使樹向左增長深度。確實有可能存在由左節點造成的長路徑構成的樹(實際上這更加便於合併操做),故此,咱們便有了左式堆這個名稱。 * 定理: 在右路徑上有 $ r $ 個節點的左式樹必然至少有 $ 2^r - 1 $ 個節點。 證實:數學概括法。 若$ r = 1 $ ,則必然至少存在一個樹節點; 假設定理對 $ r = k $ 成立,考慮在右路徑上有 $ k + 1 $ 個節點的左式樹,此時,根具備在右路徑上含 $ k $ 個節點的右子樹,以及在右路徑上至少包含 $ k $ 個節點的左式樹(不然它便不是左式樹)。對這兩個子樹應用概括假設,得知每棵子樹上最少含有 $ 2^k - 1 $ 個節點,再加上根節點,因而這顆樹上至少有有 $ 2^{k+1} - 1 $ 個節點。 原命題得證。 * 推廣:從上述定理咱們當即能夠獲得,$ N $ 個節點的左式樹有一條右路徑最多包含 $ \lfloor log(N+1) \rfloor $ 個節點。 **6.6.1 左式堆的操做** * 合併 首先,注意,插入只是合併的一個特殊情形。首先,咱們給出一個簡單的遞歸解法,而後介紹如何可以非遞歸地施行該解法。以下圖,咱們輸入兩個左式堆 $ H1 $,$ H2 $.  除去使用數據、左指針、右指針外還須要一個只是零路徑長的項。 1. 若是這兩個堆中有一個是空的,那麼咱們能夠直接返回另外一個非空的堆。 1. 不然,想要合併兩個堆,咱們須要比較它們的根。回想一下,最小堆中根節點小於它的兩個兒子,而且子樹都是堆。咱們將具備大的根值得堆與具備小的根值得堆的右子樹合併。在本例中,咱們遞歸地將 $ H2 $ 與 $ H1 $ 中根在 8 處的右子堆合併,獲得下圖:  3.注意,由於這顆樹是經過遞歸造成的,咱們有理由說,合成的樹依然是一棵左式樹。如今,咱們讓這個新堆成爲 $ H_1 $ 中根的右兒子。以下圖:圖片 4.最終獲得的堆依然知足堆序的性質,可是,它並非左式堆。由於根的左子樹的零路徑長爲 1 ,而根的右子樹的零路徑長爲 2 .左式樹的性質在根處遭到了破壞。不過,很容易看到,樹的其他部分必然是左式樹。這樣一來,咱們只要對根部進行調整便可。 方法以下:只要交換根的作兒子和右兒子,以下圖,並更新零路徑長,就完成了 Merge . 新的零路徑長是新的右兒子的零路徑長加 1 .注意,若是零路徑長不更新,那麼全部的零路徑長將都是 0 ,而堆也再也不是左式堆,只是隨機的。 
struct TreeNode;
typedef struct TreeNode *PriorityQueue;
struct TreeNode{
ElementType Element;
PriorityQueue Left;
PriorityQueue Right;
int Npl;
};
PriorityQueue Initialize( void);
ElementType FindMin( PriorityQueue H);
int IsEmpty( PriorityQueue H);
PriorityQueue Merge( PriorityQueue H1, PriorityQueue H2);
PriorityQueue Merge1( PriorityQueue H1, PriorityQueue H2)
#define Insert( X, H)( H = Insert1( ( X), H)
//宏Insert 完成一次與二叉堆兼容的插入操做
PriorityQueue Insert1( ElementType X, PriorityQueue H);
//Insert1 左式堆的插入例程
PriorityQueue DeleyeMin1( PriorityQueue H);
//合併
PriorityQueue Merge( PriorityQueue H1, PriorityQueue H2){
if( H1 == NULL)
return H2;
if( H2 == NULL)
return H1;
if( H1->Element < H2->Element);
return Merge1( H1, H2);
else
return Merge1( H2, H1);
}
PriorityQueue Merge1( PriorityQueue H1, PriorityQueue H2){
if( H1->Left == NULL)
H1->Left = H2;
else{
H1->Right = Merge( H1->Right, H2);
if( H1->Left->Npl < H1->Right->Npl)
SwapChildren( H1);
H1->Npl = H1->Right->Npl + 1;
}
return H1;
}
//插入
PriorityQueue Insert1( ElementType X, PriorityQueue H){
PriorityQueue SingleNode;
SingleNode = malloc( sizeof( struct TreeNode));
if( SingleNode == NULL)
FatalError(" Out of space");
else{
SingleNode->Element = X;
SingleNode->Npl = 0;
SingleNode->Left = SingleNode->Right = NULL;
H = Merge( SingleNode, H);
}
return H;
}
//刪除
PriorityQueue DeleyeMin1( PriorityQueue H){
PriorityQueue LeftHeap, RightHeap;
is( IsEmpty( H)){
Error(" Priority queue is empty");
return H;
}
LeftHeap = H->Left;
RightHeap = H->Right;
free( H);
return Merge( LeftHeap,RightHeap);
}
**6.7 斜堆** * 斜堆(skew heap):斜堆是具備堆序的二叉樹,可是不存在對樹的結構限制。它是左式堆的自調節形式,但不一樣於左式堆,關於任意節點的零路徑長的任何信息不作保留。斜堆的右路徑在任什麼時候候均可以任意長,所以,全部操做的最壞情形運行時間爲 $ O(N) $ . 與左式堆相同,斜堆的基本操做也是 Merge 合併。可是有一處例外,對於左式堆,咱們查看是否左兒子和右兒子知足左式堆堆序性質並交換那些不知足性質者;對於斜堆,除了這些右路徑上全部節點的最大者不交換它們的左右兒子以外,交換是無條件的。 **6.8 二項隊列** **6.8.1 二項隊列結構** * 二項隊列(binomial queue):一個二項隊列不是一棵堆序的樹,而是堆序樹的集合,稱爲森林(forest). * 堆序樹中的每一棵都是有約束的形式,叫作二項樹(binomial tree). * 每個高度上之多存在一棵二項樹。高度爲 0 的二項樹是一顆單節點樹;高度爲 k 的二項樹 $ Bk $ 是經過將一棵二項樹 $ B{k-1} $ 附接到另外一棵二項樹 $ B{k-1} $ 的根上而構成的。以下圖:二項樹 $ B0、B一、B二、B三、B4 $ .  從圖中看到,二項樹 $ Bk $ 由一個帶有兒子 $ B0 , B1, B2,..., B{k-1} $ 的根組成。高度爲 k 的二項樹剛好有 $ 2^k $ 個節點,而在深度 d 處的節點數爲 $ Ck^d $ . * 若是咱們把堆序施加到二項樹上並容許任意高度上最多有一棵二項樹,那麼咱們可以用二項樹的集合唯一地表示任意大小地優先隊列。 for example:大小爲 13 的優先隊列能夠用森林 $ B0 , B2, B3 $ 表示。咱們能夠把這種表示寫成 1101 ,這不只以二進制表示了 13 也表述 $ B1 $ 樹不存在的事實。 **6.8.2 二項隊列的操做** * FindMin:能夠經過搜索全部樹的樹根找出。因爲最多有 $ logN $ 棵不一樣的樹,所以找到最小元的時間複雜度爲 $ O(logN) $ . 另外,若是咱們記住當最小元在其餘操做期間變化時更新它,那麼咱們也可保留最小元的信息並以 $ O(1) $ 時間執行該操做。 * Merge:合併操做基本上是經過將兩個隊列加到一塊兒來完成的。考慮兩個二項隊列 $ H1,H2 $ ,他們分別具備六個和七個元素,見下圖。  令 $ H_3 $ 是新的二項隊列。 * 因爲 $ H1 $ 沒有高度爲 0 的二項樹而 $ H2 $ 擁有,所以咱們就用 $ H2 $ 中高度爲 0 的二項樹做爲 $ H3 $ 的一部分。 * 因爲 $ H一、H2 $ 都擁有高度爲 1 的二項樹,所以咱們令兩者合稱爲 $ H_3 $ 中高度爲 2 的二項樹。 * 現存有三棵高度爲 2 的樹,咱們選擇其中兩個和合成高度爲 3 的樹,另一棵放到 $ H_3 $ 中。 * 因爲如今 $ H一、H2 $ 不存在高度爲 3 的樹,合併結束。 $ H_3 $ 以下圖:  考慮 Merge 操做的時間複雜度,因爲幾乎使用任意合理的實現方法合併兩棵二項樹均花費常數時間,而總存在 $ O(logN) $ 棵二項樹,所以合併在最壞情形下花費時間爲 $ O(logN) $ .爲了使操做更高效,咱們須要將這些樹放到按照高度排血的二項隊列中。 * Insert:插入操做其實是特殊情形的合併,咱們只須要建立一棵單節點樹並執行一次合併操做。這種操做的最壞運行時間也是 $ O(logN) $ .更加準確地說,若是元素將要插入的那個優先隊列不存在的最小的 $ B_k $ ,那麼運行時間與 i+1 成正比. * DeleteMin:經過首先找出一棵具備最小根的二項樹來完成。令該樹爲 $ Bk $ ,並令原始的優先隊列爲 $ H $ ,咱們從 H 的樹的森林中除去二項樹 $ Bk $ ,造成新的二項樹隊列 $ H' $ ,再除去 $ Bk $ 的根,獲得一些二項樹 $ B0 , B1, B2,..., B{k-1} $ ,它們共同造成優先隊列 $ H'' $ .合成 $ H' $ 與 $ H'' $ ,操做結束。 例如,假設有二項隊列 $ H3 $ ,以下圖:  其中最小的根是 12,所以咱們獲得兩個優先隊列 $ H' $ 和 $ H'' $ ,以下圖:  最後,合併 $ H' $ 和 $ H'' $ ,完成 DeleteMin 操做。  分析時間複雜度,注意,DeleteMin 操做將原隊列一分爲二,找出含有最小元素的樹並建立隊列 $ H' $ 和 $ H'' $ 花費時間爲 $ O(logN) $ 時間,合併 $ H' $ 和 $ H'' $ 又花費時間爲 $ O(logN) $ 時間。所以,整個 DeleteMin 操做的時間複雜度爲 $ O(logN) $ . **6.8.3 二項隊列的實現** * 二項樹的每個節點包含一個數據,第一個兒子以及右兄弟。二項樹中的諸兒子以遞增的次序排列 
//聲明
typedef struct BinNode Position;
typedef struct Collection BinQueue;
struct BinNode{
ElementType Element;
Position LeftChild;
Position NextSibling;
};
struct Collection{
int CurrentSize;
BinTree TheTrees[ MaxTrees];
};
//合併兩顆一樣大小的二項樹
BinTree CombineTrees( BinTree T1, BinTree T2){
if( T1->Element > T2->Element)
return ConbinTrees( T2, T1);
T2->NextSibling = T1->LeftChild;
T1->LeftChild = T2;
return T1;
}
//合併
BinQueue Merge( BinQueue H1, BinQueue H2){
BinTree T1, T2, Carry = NULL;
int i,j;
if( H1->CurrentSize + H2->CurrentSize > Capacity)
Error("Merge would exceed capacity");
H1->CurrentSize += H2->CurrentSize;
for( i = 0, j = 1; j <= H1->CurrentSize; i++){
T1 = H1->TheTrees[i];
T2 = H2->TheTrees[i];
switch( !!T1 + 2 !!T2 + 4 !!Carry){//Carry 是上一步驟獲得的樹 case 0: //No trees; case 1: break; case 2: H1->TheTrees[i] = T2; H2->TheTrees[i] = NULL; case 4: //Only Carry H1->TheTrees[i] = Carry; break; case 3: Carry = CombineTrees( T1, T2); H1->TheTrees[i] = H2->TheTrees[i] = NULL; break; case 5: Carry = CombineTrees( T1, T2); H1->TheTrees[i] = NULL; break; case 6: Carry = CombineTrees( T1, T2); H2->TheTrees[i] = NULL; break; case 7: //All three H1->TheTrees[i] = Carry; Carry = CombineTrees( T1, T2); H2->TheTrees[i] = NULL; break; } } return H1;}//刪除最小元並返回ElementType DeleteMin( BinQueue H){ int i,j; int MinTree; BinQueue DeletedQueue; Position DeletedTree, OldRoot; ElementType MinItem; if( IsEmpty( H)){ Error(" Empty binimial queue"); return -Infinity; } MinItem = I