這三篇文章主要講解二項堆、斐波那契堆、Pairing 堆,這三種結構主要用於優先隊列的實現。資料主要參考《演算法導論》和或互連網。
《演算法導論》第19章講到了二項堆
1.二項堆簡介
二項堆是二項堆是由一組二項樹組成,在給出二項堆的定義之前,首先我們來定義什麼是二項樹。
1.1二項樹
二項樹是一種遞迴的有序樹。定義如下:
1. 二項樹B[0]僅僅包含一個節點
2. B[k]是由兩棵B[k-1]二項樹組成,其中一顆樹是另外一顆樹的左孩子。
下面(a)是二項樹B[k]遞迴定義,(b)二項樹B[0]到B[4],(c)看待二項樹的另一種方式:
二項樹的性質:
1. 對於樹B[k]該樹含有2^k個節點;
2. 樹的高度是k;
3. 在深度為i中含有Cik節點,其中i = 0, 1,2 ... , k;
4. 根的度數為k,最大,
推論:n節點的二項樹,任意節點的最大度數為log(n)
1.2二項堆
二項堆是由一組滿足下面的二項樹組成:
1. H中的每個二項樹遵循最小堆性質(類似小頂堆);
2. 對於任意的整數k的話,在H只存在一個度數是k的二項樹;
另外定義:
1. 二項堆的度數定義成子女個數;
2. 定義二項堆的根表是二項樹的根節點形成的鏈表;
2.資料結構
瞭解了什麼二項堆的定義和使用情境之後,我們來看看如何儲存二項堆?
首先定義二項堆中每個節點的類型:
1. parent:指向父節點
2. sibling:指向右邊的兄弟節點
3. child:定義該節點的子節點
4. degree:定義該節點的度數
5. 其他應用情境中需要的資料
struct heap_node{struct heap_node*parent;struct heap_node*next;struct heap_node*child;unsigned intdegree;void*value;struct heap_node**ref;};
對於定義的欄位,對於根表中的節點和非根表中的元素是不相同的,根表中的節點的parent全部是空,sibling指向的是根表中的下一個元素;對於非根表中的節點的話,parent指向的是該節點的父節點,sibling指向該節點的兄弟節點。
定義二項堆資料類型:
1. 根表的前端節點
2. 其他應用需要的資料
struct heap {struct heap_node*head;struct heap_node*min;};
3.二項堆操作
3.1建立一個新的二項堆
MAKE-BINOMIAL-HEAP,
分配並返回一個對象H,head[H]=NIL;running time is(1).
僅僅將根表的前端節點指向null:
static inline void heap_init(struct heap* heap){heap->head = NULL;heap->min = NULL;// 其他動作
}
3.2尋找最小關鍵字
BINOMIAL-HEAP-MINIMUM,
返回一個指標,指向最小關鍵字節點。
由於二項堆中每個二項樹都是遵循最小堆性質的,所以最小元素一定是在根表中,遍曆根表一次即可找出最小元素:
BINOMIAL-HEAP-MINIMUM(H)
1 y NIL
2 x head[H]
3 min
4 while x NIL
5 do if key[x] < min
6 then min key[x]
7 y x
8 x sibling[x]
9 return y
3.3 合併作業(MOST)
BINOMIAL-HEAP-UNION
二項堆的合併作業是一個比較複雜,但是至關重要的一個過程,這裡假設需要合并的兩個二項堆是H1和H2,如果簡單的將H1和H2的根表(兩個鏈表)進行合并的話,顯然這可能是違反了二項堆定義的第二條:
2. B[k]是由兩棵B[k-1]二項樹組成,其中一顆樹是另外一顆樹的子樹。
這就是二項堆合併作業的主要需要解決的問題:兩個二項堆合并完成之後,可能在根表中存在兩個度數相同的節點,需要將度數相同的節點合并成一個新的節點。
這裡我們進一步將這個問題現在轉換成了:已知H1根表和H2的根表中每個節點的度數,並且H1和H2的根表已經是按照度數排序的,如何H1和H2根表合并,並且新的根表中不存在兩個度數相同的節點。
一個樸素的思想就是現將H1和H2合并,然後在進行適當的調整,將兩個度數相同的節點合并掉,這樣保證了新的根表中不存你在兩個度數相同的節點。那麼基本的程式架構:
union(H1, H2)
{
#1:合并H1和H2的根表,產生新的根表H,並且H的節點是按照度數排序的;
#2:將H中度數相同的兩個節點合并,直到根表H中不存在兩個度數相同的節點;
}
繼續細化上面的思路,#1是比較簡單的,類似于歸並排序中的合并的思路,但是對於#2而言,由於H是排序完成的,那麼我們僅僅需要將H遍曆一遍,如果存在兩個相同的度數,合并即可。
case1: 當前節點的degree[x]與其後繼結點的degree[next-x]不相等;後移;
case2: 當前節點的degree[x]與其後繼的後繼degree[sibling[next-x]]相等;後移;(三個深度都一樣)
else:
degree[x] = degree[next-x]:
case3: if(key[x] <= key[next-x]) : sibling[x] = sibling[next-x], BINOMIAL-LINK(next-x,x)//next-x賦給x的左孩子
case4: if(key[x] > key[next-x]) : sibling[prev-x] = next-x, BINOMIAL-LINK(x,next-x)//x賦給next-x的左孩子
3.4 插入一個節點
BINOMIAL-HEAP-INSERT(H,x)
首先構建一個只包含一個元素的二項堆H',O(1),再合并,O(log(n));
1 H' MAKE-BINOMIAL-HEAP()
2 p[x] NIL
3 child[x] NIL
4 sibling[x] NIL
5 degree[x] 0
6 head[H'] x
7 H BINOMIAL-HEAP-UNION(H,H')
3.5 抽取最小元素
BINOMIAL-HEAP-EXTRACT-MIN
1 find the root x with the minimum key in the root list of H, MAKE-BINOMIAL-HEAP
and remove x from the root list of H
2 H' MAKE-BINOMIAL-HEAP()
3 reverse the order of the linked list of x's children,
and set head[H'] to point to the head of the resulting list 逆向串連x的孩子到head[H']
4 H BINOMIAL-HEAP-UNION(H,H')合并
5 return x
3.6 減小一個關鍵字的值
BINOMIAL-HEAP-DECREASE-KEY (H,x,k)
將y減小到7,冒泡上升即可。
3.7 刪除一個關鍵字
BINOMIAL-HEAP-DELETE(H,x)
將刪除元素當做無窮小,利用 BINOMIAL-HEAP-DECREASE-KEY (H,x,k)冒泡上升到樹根,再刪除BINOMIAL-HEAP-EXTRACT-MIN(H)
1 BINOMIAL-HEAP-DECREASE-KEY(H,x,-)
2 BINOMIAL-HEAP-EXTRACT-MIN(H)
4.應用情況
我們在什麼情況下使用“二項堆”?
如果是不支援所謂的合併作業union的話,普通的堆的資料結構就是一種很理想的資料結構。 但是如果想要支援集合上的合併作業的話,最好是使用二項堆或者是斐波那契堆,普通的堆在union操作上最差的情況是O(n),但是二項堆和斐波那契堆是O(lgn)。
Binary heap Binomial heap() Fibonacci heap
二元堆積(最壞情況) 二項堆(最壞情況) (斐波那契堆(平攤))
Procedure (worst-case) (worst-case) (amortized)
--------------------------------------------------------------
MAKE-HEAP (1) (1) (1)
INSERT (lg n) O(lg n) (1)
MINIMUM (1) O(lg n) (l)
EXTRACT-MIN (lg n) (1g n) O(lg n)
UNION (n) O(lg n) (1)
DECREASE-KEY (lg n) (lg n) (1)
DELETE (1g n) (lg n) O(lg n)
5.參考資料
演算法導論:http://net.pku.edu.cn/~course/cs101/2007/resource/Intro2Algorithm/book6/chap20.htm
部落格園(內含代碼):http://www.cnblogs.com/xuqiang/archive/2011/06/01/2065549.html
ITEYE(完整代碼):http://dsqiu.iteye.com/blog/1714961