本章介紹二項堆,它和以前所講的堆(二叉堆、左傾堆、斜堆)同樣,也是用於實現優先隊列的。和以往同樣,本文會先對二項堆的理論知識進行簡單介紹,而後給出C語言的實現。後續再分別給出C++和Java版本的實現;實現的語言雖不一樣,可是原理同樣,選擇其中之一進行了解便可。若文章有錯誤或不足的地方,請不吝指出! html
目錄
1. 二項樹的介紹
2. 二項堆的介紹
3. 二項堆的基本操做
4. 二項堆的C實現(完整源碼)
5. 二項堆的C測試程序node
轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3655900.html算法
更多內容:數據結構與算法系列 目錄數據結構
(01) 二項堆(一)之 圖文解析 和 C語言的實現
(02) 二項堆(二)之 C++的實現
(03) 二項堆(二)之 Java的實現ide
二項樹的定義函數
二項堆是二項樹的集合。在瞭解二項堆以前,先對二項樹進行介紹。測試
二項樹是一種遞歸定義的有序樹。它的遞歸定義以下:
(01) 二項樹B0只有一個結點;
(02) 二項樹Bk由兩棵二項樹B(k-1)組成的,其中一棵樹是另外一棵樹根的最左孩子。
以下圖所示:spa
上圖的B0、B1、B2、B3、B4都是二項樹。對比前面提到的二項樹的定義:B0只有一個節點,B1由兩個B0所組成,B2由兩個B1所組成,B3由兩個B2所組成,B4由兩個B3所組成;並且,當兩顆相同的二項樹組成另外一棵樹時,其中一棵樹是另外一棵樹的最左孩子。3d
二項樹的性質code
二項樹有如下性質:
[性質一] Bk共有2k個節點。
[性質二] Bk的高度爲k。
[性質三] Bk在深度i處剛好有C(k,i)個節點,其中i=0,1,2,...,k。
[性質四] 根的度數爲k,它大於任何其它節點的度數。
注意:樹的高度和深度是相同的。關於樹的高度的概念,《算法導論》中只有一個節點的樹的高度是0,而"維基百科"中只有一個節點的樹的高度是1。本文使用了《算法導論中》"樹的高度和深度"的概念。
下面對這幾個性質進行簡單說明:
[性質一] Bk共有2k個節點。
如上圖所示,B0有20=1節點,B1有21=2個節點,B2有22=4個節點,...
[性質二] Bk的高度爲k。
如上圖所示,B0的高度爲0,B1的高度爲1,B2的高度爲2,...
[性質三] Bk在深度i處剛好有C(k,i)個節點,其中i=0,1,2,...,k。
C(k,i)是高中數學中階乘元素,例如,C(10,3)=(10*9*8) / (3*2*1)=240
B4中深度爲0的節點C(4,0)=1
B4中深度爲1的節點C(4,1)= 4 / 1 = 4
B4中深度爲2的節點C(4,2)= (4*3) / (2*1) = 6
B4中深度爲3的節點C(4,3)= (4*3*2) / (3*2*1) = 4
B4中深度爲4的節點C(4,4)= (4*3*2*1) / (4*3*2*1) = 1
合計獲得B4的節點分佈是(1,4,6,4,1)。
[性質四] 根的度數爲k,它大於任何其它節點的度數。
節點的度數是該結點擁有的子樹的數目。
二項堆一般被用來實現優先隊列,它堆是指知足如下性質的二項樹的集合:
(01) 每棵二項樹都知足最小堆性質。即,父節點的關鍵字 <= 它的孩子的關鍵字。
(02) 不能有兩棵或以上的二項樹具備相同的度數(包括度數爲0)。換句話說,具備度數k的二項樹有0個或1個。
上圖就是一棵二項堆,它由二項樹B0、B2和B3組成。對比二項堆的定義:(01)二項樹B0、B2、B3都是最小堆;(02)二項堆不包含相同度數的二項樹。
二項堆的第(01)個性質保證了二項堆的最小節點是某一棵二項樹的根節點,第(02)個性質則說明結點數爲n的二項堆最多隻有log{n} + 1棵二項樹。實際上,將包含n個節點的二項堆,表示成若干個2的指數和(或者轉換成二進制),則每個2個指數都對應一棵二項樹。例如,13(二進制是1101)的2個指數和爲13=23 + 22 + 20, 所以具備13個節點的二項堆由度數爲3, 2, 0的三棵二項樹組成。
二項堆是可合併堆,它的合併操做的複雜度是O(log n)。
1. 基本定義
1 #ifndef _BINOMIAL_HEAP_H_ 2 #define _BINOMIAL_HEAP_H_ 3 4 typedef int Type; 5 6 typedef struct _BinomialNode{ 7 Type key; // 關鍵字(鍵值) 8 int degree; // 度數 9 struct _BinomialNode *child; // 左孩子 10 struct _BinomialNode *parent; // 父節點 11 struct _BinomialNode *next; // 兄弟 12 }BinomialNode, *BinomialHeap; 13 14 // 新建key對應的節點,並將其插入到二項堆中。 15 BinomialNode* binomial_insert(BinomialHeap heap, Type key); 16 // 刪除節點:刪除鍵值爲key的節點,並返回刪除節點後的二項樹 17 BinomialNode* binomial_delete(BinomialHeap heap, Type key); 18 // 將二項堆heap的鍵值oldkey更新爲newkey 19 void binomial_update(BinomialHeap heap, Type oldkey, Type newkey); 20 21 // 合併二項堆:將h1, h2合併成一個堆,並返回合併後的堆 22 BinomialNode* binomial_union(BinomialHeap h1, BinomialHeap h2) ; 23 24 // 查找:在二項堆中查找鍵值爲key的節點 25 BinomialNode* binomial_search(BinomialHeap heap, Type key); 26 // 獲取二項堆中的最小節點 27 BinomialNode* binomial_minimum(BinomialHeap heap) ; 28 // 移除最小節點,並返回移除節點後的二項堆 29 BinomialNode* binomial_extract_minimum(BinomialHeap heap); 30 31 // 打印"二項堆" 32 void binomial_print(BinomialHeap heap); 33 34 #endif
BinomialNode是二項堆的節點。它包括了關鍵字(key),用於比較節點大小;度數(degree),用來表示當前節點的度數;左孩子(child)、父節點(parent)以及兄弟節點(next)。
下面是一棵二項堆的樹形圖和它對應的內存結構關係圖。
2. 合併操做
合併操做是二項堆的重點,二項堆的添加操做也是基於合併操做來實現的。
合併兩個二項堆,須要的步驟歸納起來以下:
(01) 將兩個二項堆的根鏈表合併成一個鏈表。合併後的新鏈表按照"節點的度數"單調遞增排列。
(02) 將新鏈表中"根節點度數相同的二項樹"鏈接起來,直到全部根節點度數都不相同。
下面,先看看合併操做的代碼;而後再經過示意圖對合並操做進行說明。
binomial_merge()代碼(C語言)
1 /* 2 * 將h1, h2中的根表合併成一個按度數遞增的鏈表,返回合併後的根節點 3 */ 4 static BinomialNode* binomial_merge(BinomialHeap h1, BinomialHeap h2) 5 { 6 BinomialNode* head = NULL; //heap爲指向新堆根結點 7 BinomialNode** pos = &head; 8 9 while (h1 && h2) 10 { 11 if (h1->degree < h2->degree) 12 { 13 *pos = h1; 14 h1 = h1->next; 15 } 16 else 17 { 18 *pos = h2; 19 h2 = h2->next; 20 } 21 pos = &(*pos)->next; 22 } 23 if (h1) 24 *pos = h1; 25 else 26 *pos = h2; 27 28 return head; 29 }
binomial_link()代碼(C語言)
1 /* 2 * 合併兩個二項堆:將child合併到heap中 3 */ 4 static void binomial_link(BinomialHeap child, BinomialHeap heap) 5 { 6 child->parent = heap; 7 child->next = heap->child; 8 heap->child = child; 9 heap->degree++; 10 }
合併操做代碼(C語言)
1 /* 2 * 合併二項堆:將h1, h2合併成一個堆,並返回合併後的堆 3 */ 4 BinomialNode* binomial_union(BinomialHeap h1, BinomialHeap h2) 5 { 6 BinomialNode *heap; 7 BinomialNode *prev_x, *x, *next_x; 8 9 // 將h1, h2中的根表合併成一個按度數遞增的鏈表heap 10 heap = binomial_merge(h1, h2); 11 if (heap == NULL) 12 return NULL; 13 14 prev_x = NULL; 15 x = heap; 16 next_x = x->next; 17 18 while (next_x != NULL) 19 { 20 if ( (x->degree != next_x->degree) 21 || ((next_x->next != NULL) && (next_x->degree == next_x->next->degree))) 22 { 23 // Case 1: x->degree != next_x->degree 24 // Case 2: x->degree == next_x->degree == next_x->next->degree 25 prev_x = x; 26 x = next_x; 27 } 28 else if (x->key <= next_x->key) 29 { 30 // Case 3: x->degree == next_x->degree != next_x->next->degree 31 // && x->key <= next_x->key 32 x->next = next_x->next; 33 binomial_link(next_x, x); 34 } 35 else 36 { 37 // Case 4: x->degree == next_x->degree != next_x->next->degree 38 // && x->key > next_x->key 39 if (prev_x == NULL) 40 { 41 heap = next_x; 42 } 43 else 44 { 45 prev_x->next = next_x; 46 } 47 binomial_link(x, next_x); 48 x = next_x; 49 } 50 next_x = x->next; 51 } 52 53 return heap; 54 }
合併函數binomial_union(h1, h2)的做用是將h1和h2合併,並返回合併後的二項堆。在binomial_union(h1, h2)中,涉及到了兩個函數binomial_merge(h1, h2)和binomial_link(child, heap)。
binomial_merge(h1, h2)就是咱們前面所說的"兩個二項堆的根鏈表合併成一個鏈表,合併後的新鏈表按照'節點的度數'單調遞增排序"。
binomial_link(child, heap)則是爲了合併操做的輔助函數,它的做用是將"二項堆child的根節點"設爲"二項堆heap的左孩子",從而將child整合到heap中去。
在binomial_union(h1, h2)中對h1和h2進行合併時;首先經過 binomial_merge(h1, h2) 將h1和h2的根鏈表合併成一個"按節點的度數單調遞增"的鏈表;而後進入while循環,對合並獲得的新鏈表進行遍歷,將新鏈表中"根節點度數相同的二項樹"鏈接起來,直到全部根節點度數都不相同爲止。在將新聯表中"根節點度數相同的二項樹"鏈接起來時,能夠將被鏈接的狀況歸納爲4種。
x是根鏈表的當前節點,next_x是x的下一個(兄弟)節點。
Case 1: x->degree != next_x->degree
即,"當前節點的度數"與"下一個節點的度數"相等時。此時,不須要執行任何操做,繼續查看後面的節點。
Case 2: x->degree == next_x->degree == next_x->next->degree
即,"當前節點的度數"、"下一個節點的度數"和"下下一個節點的度數"都相等時。此時,暫時不執行任何操做,仍是繼續查看後面的節點。實際上,這裏是將"下一個節點"和"下下一個節點"等到後面再進行整合鏈接。
Case 3: x->degree == next_x->degree != next_x->next->degree
&& x->key <= next_x->key
即,"當前節點的度數"與"下一個節點的度數"相等,而且"當前節點的鍵值"<="下一個節點的度數"。此時,將"下一個節點(對應的二項樹)"做爲"當前節點(對應的二項樹)的左孩子"。
Case 4: x->degree == next_x->degree != next_x->next->degree
&& x->key > next_x->key
即,"當前節點的度數"與"下一個節點的度數"相等,而且"當前節點的鍵值">"下一個節點的度數"。此時,將"當前節點(對應的二項樹)"做爲"下一個節點(對應的二項樹)的左孩子"。
下面經過示意圖來對合並操做進行說明。
第1步:將兩個二項堆的根鏈表合併成一個鏈表
執行完第1步以後,獲得的新鏈表中有許多度數相同的二項樹。實際上,此時獲得的是對應"Case 4"的狀況,"樹41"(根節點爲41的二項樹)和"樹13"的度數相同,且"樹41"的鍵值 > "樹13"的鍵值。此時,將"樹41"做爲"樹13"的左孩子。
第2步:合併"樹41"和"樹13"
執行完第2步以後,獲得的是對應"Case 3"的狀況,"樹13"和"樹28"的度數相同,且"樹13"的鍵值 < "樹28"的鍵值。此時,將"樹28"做爲"樹13"的左孩子。
第3步:合併"樹13"和"樹28"
執行完第3步以後,獲得的是對應"Case 2"的狀況,"樹13"、"樹28"和"樹7"這3棵樹的度數都相同。此時,將x設爲下一個節點。
第4步:將x和next_x日後移
執行完第4步以後,獲得的是對應"Case 3"的狀況,"樹7"和"樹11"的度數相同,且"樹7"的鍵值 < "樹11"的鍵值。此時,將"樹11"做爲"樹7"的左孩子。
第5步:合併"樹7"和"樹11"
執行完第5步以後,獲得的是對應"Case 4"的狀況,"樹7"和"樹6"的度數相同,且"樹7"的鍵值 > "樹6"的鍵值。此時,將"樹7"做爲"樹6"的左孩子。
第6步:合併"樹7"和"樹6"
此時,合併操做完成!
PS. 合併操做的圖文解析過程與"二項堆的測試程序(main.c)中的test_union()函數"是對應的!
3. 插入操做
理解了"合併"操做以後,插入操做就至關簡單了。插入操做能夠看做是將"要插入的節點"和當前已有的堆進行合併。
插入操做代碼(C語言)
1 /* 2 * 新建key對應的節點,並將其插入到二項堆中。 3 * 4 * 參數說明: 5 * heap -- 原始的二項樹。 6 * key -- 鍵值 7 * 返回值: 8 * 插入key以後的二項樹 9 */ 10 BinomialNode* binomial_insert(BinomialHeap heap, Type key) 11 { 12 BinomialNode* node; 13 14 if (binomial_search(heap, key) != NULL) 15 { 16 printf("insert failed: the key(%d) is existed already!\n", key); 17 return heap; 18 } 19 20 node = make_binomial_node(key); 21 if (node==NULL) 22 return heap; 23 24 return binomial_union(heap, node); 25 }
在插入時,首先經過binomial_search(heap, key)查找鍵值爲key的節點。存在的話,則直接返回;不存在的話,則經過make_binomial_node(key)新建鍵值爲key的節點node,而後將node和heap進行合併。
注意:我這裏實現的二項堆是"進制插入相同節點的"!若你想容許插入相同鍵值的節點,則屏蔽掉插入操做中的binomial_search(heap, key)部分代碼便可。
4. 刪除操做
刪除二項堆中的某個節點,須要的步驟歸納起來以下:
(01) 將"該節點"交換到"它所在二項樹"的根節點位置。方法是,從"該節點"不斷向上(即向樹根方向)"遍歷,不斷交換父節點和子節點的數據,直到被刪除的鍵值到達樹根位置。
(02) 將"該節點所在的二項樹"從二項堆中移除;將該二項堆記爲heap。
(03) 將"該節點所在的二項樹"進行反轉。反轉的意思,就是將根的全部孩子獨立出來,並將這些孩子整合成二項堆,將該二項堆記爲child。
(04) 將child和heap進行合併操做。
下面,先看看刪除操做的代碼;再進行圖文說明。
binomial_reverse()代碼(C語言)
1 /* 2 * 反轉二項堆heap 3 */ 4 static BinomialNode* binomial_reverse(BinomialNode* heap) 5 { 6 BinomialNode* next; 7 BinomialNode* tail = NULL; 8 9 if (!heap) 10 return heap; 11 12 heap->parent = NULL; 13 while (heap->next) 14 { 15 next = heap->next; 16 heap->next = tail; 17 tail = heap; 18 heap = next; 19 heap->parent = NULL; 20 } 21 heap->next = tail; 22 23 return heap; 24 }
刪除操做代碼(C語言)
1 /* 2 * 刪除節點:刪除鍵值爲key的節點,並返回刪除節點後的二項樹 3 */ 4 BinomialNode* binomial_delete(BinomialHeap heap, Type key) 5 { 6 BinomialNode *node; 7 BinomialNode *parent, *prev, *pos; 8 9 if (heap==NULL) 10 return heap; 11 12 // 查找鍵值爲key的節點 13 if ((node = binomial_search(heap, key)) == NULL) 14 return heap; 15 16 // 將被刪除的節點的數據數據上移到它所在的二項樹的根節點 17 parent = node->parent; 18 while (parent != NULL) 19 { 20 // 交換數據 21 swap(node->key, parent->key); 22 // 下一個父節點 23 node = parent; 24 parent = node->parent; 25 } 26 27 // 找到node的前一個根節點(prev) 28 prev = NULL; 29 pos = heap; 30 while (pos != node) 31 { 32 prev = pos; 33 pos = pos->next; 34 } 35 // 移除node節點 36 if (prev) 37 prev->next = node->next; 38 else 39 heap = node->next; 40 41 heap = binomial_union(heap, binomial_reverse(node->child)); 42 43 free(node); 44 45 return heap; 46 }
binomial_delete(heap, key)的做用是刪除二項堆heap中鍵值爲key的節點,並返回刪除節點後的二項堆。
binomial_reverse(heap)的做用是反轉二項堆heap,並返回反轉以後的根節點。
下面經過示意圖來對刪除操做進行說明(刪除二項堆中的節點20)。
總的思想,就是將被"刪除節點"從它所在的二項樹中孤立出來,而後再對二項樹進行相應的處理。
PS. 刪除操做的圖文解析過程與"二項堆的測試程序(main.c)中的test_delete()函數"是對應的!
5. 更新操做
更新二項堆中的某個節點,就是修改節點的值,它包括兩部分分:"減小節點的值" 和 "增長節點的值" 。
更新操做代碼(C語言)
1 /* 2 * 更新二項堆heap的節點node的鍵值爲key 3 */ 4 static void binomial_update_key(BinomialHeap heap, BinomialNode* node, Type key) 5 { 6 if (node == NULL) 7 return ; 8 9 if(key < node->key) 10 binomial_decrease_key(heap, node, key); 11 else if(key > node->key) 12 binomial_increase_key(heap, node, key); 13 else 14 printf("No need to update!!!\n"); 15 }
5.1 減小節點的值
減小節點值的操做很簡單:該節點必定位於一棵二項樹中,減少"二項樹"中某個節點的值後要保證"該二項樹仍然是一個最小堆";所以,就須要咱們不斷的將該節點上調。
減小操做代碼(C語言)
1 /* 2 * 減小關鍵字的值:將二項堆heap中的節點node的鍵值減少爲key。 3 */ 4 static void binomial_decrease_key(BinomialHeap heap, BinomialNode *node, Type key) 5 { 6 if ((key >= node->key) || (binomial_search(heap, key) != NULL)) 7 { 8 printf("decrease failed: the new key(%d) is existed already, \ 9 or is no smaller than current key(%d)\n", key, node->key); 10 return ; 11 } 12 node->key = key; 13 14 BinomialNode *child, *parent; 15 child = node; 16 parent = node->parent; 17 while(parent != NULL && child->key < parent->key) 18 { 19 swap(parent->key, child->key); 20 child = parent; 21 parent = child->parent; 22 } 23 }
下面是減小操做的示意圖(20->2)
減小操做的思想很簡單,就是"保持被減節點所在二項樹的最小堆性質"。
PS. 減小操做的圖文解析過程與"二項堆的測試程序(main.c)中的test_decrease()函數"是對應的!
5.2 增長節點的值
增長節點值的操做也很簡單。上面說過減小要將被減小的節點不斷上調,從而保證"被減小節點所在的二項樹"的最小堆性質;而增長操做則是將被增長節點不斷的下調,從而保證"被增長節點所在的二項樹"的最小堆性質。
增長操做代碼(C語言)
1 /* 2 * 增長關鍵字的值:將二項堆heap中的節點node的鍵值增長爲key。 3 */ 4 static void binomial_increase_key(BinomialHeap heap, BinomialNode *node, Type key) 5 { 6 if ((key <= node->key) || (binomial_search(heap, key) != NULL)) 7 { 8 printf("increase failed: the new key(%d) is existed already, \ 9 or is no greater than current key(%d)\n", key, node->key); 10 return ; 11 } 12 node->key = key; 13 14 BinomialNode *cur, *child, *least; 15 cur = node; 16 child = cur->child; 17 while (child != NULL) 18 { 19 if(cur->key > child->key) 20 { 21 // 若是"當前節點" < "它的左孩子", 22 // 則在"它的孩子中(左孩子 和 左孩子的兄弟)"中,找出最小的節點; 23 // 而後將"最小節點的值" 和 "當前節點的值"進行互換 24 least = child; 25 while(child->next != NULL) 26 { 27 if (least->key > child->next->key) 28 { 29 least = child->next; 30 } 31 child = child->next; 32 } 33 // 交換最小節點和當前節點的值 34 swap(least->key, cur->key); 35 36 // 交換數據以後,再對"原最小節點"進行調整,使它知足最小堆的性質:父節點 <= 子節點 37 cur = least; 38 child = cur->child; 39 } 40 else 41 { 42 child = child->next; 43 } 44 } 45 }
下面是增長操做的示意圖(6->60)
增長操做的思想很簡單,"保持被增長點所在二項樹的最小堆性質"。
PS. 增長操做的圖文解析過程與"二項堆的測試程序(main.c)中的test_increase()函數"是對應的!
注意:關於二項堆的"查找"、"打印"等其它接口就再也不單獨介紹了,後文的源碼中有給出它們的實現代碼。有興趣的話,Please RTFSC(Read The Fucking Source Code)!
二項堆的頭文件(binomial_heap.h)
1 #ifndef _BINOMIAL_HEAP_H_ 2 #define _BINOMIAL_HEAP_H_ 3 4 typedef int Type; 5 6 typedef struct _BinomialNode{ 7 Type key; // 關鍵字(鍵值) 8 int degree; // 度數 9 struct _BinomialNode *child; // 左孩子 10 struct _BinomialNode *parent; // 父節點 11 struct _BinomialNode *next; // 兄弟 12 }BinomialNode, *BinomialHeap; 13 14 // 新建key對應的節點,並將其插入到二項堆中。 15 BinomialNode* binomial_insert(BinomialHeap heap, Type key); 16 // 刪除節點:刪除鍵值爲key的節點,並返回刪除節點後的二項樹 17 BinomialNode* binomial_delete(BinomialHeap heap, Type key); 18 // 將二項堆heap的鍵值oldkey更新爲newkey 19 void binomial_update(BinomialHeap heap, Type oldkey, Type newkey); 20 21 // 合併二項堆:將h1, h2合併成一個堆,並返回合併後的堆 22 BinomialNode* binomial_union(BinomialHeap h1, BinomialHeap h2) ; 23 24 // 查找:在二項堆中查找鍵值爲key的節點 25 BinomialNode* binomial_search(BinomialHeap heap, Type key); 26 // 獲取二項堆中的最小節點 27 BinomialNode* binomial_minimum(BinomialHeap heap) ; 28 // 移除最小節點,並返回移除節點後的二項堆 29 BinomialNode* binomial_extract_minimum(BinomialHeap heap); 30 31 // 打印"二項堆" 32 void binomial_print(BinomialHeap heap); 33 34 #endif
二項堆的實現文件(binomial_heap.c)
1 /** 2 * C語言實現的二項堆 3 * 4 * @author skywang 5 * @date 2014/04/01 6 */ 7 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include "binomial_heap.h" 11 12 #define swap(a,b) (a^=b,b^=a,a^=b) 13 14 /* 15 * 查找:在二項堆中查找鍵值爲key的節點 16 */ 17 BinomialNode* binomial_search(BinomialHeap heap, Type key) 18 { 19 BinomialNode *child; 20 BinomialNode *parent = heap; 21 22 parent = heap; 23 while (parent != NULL) 24 { 25 if (parent->key == key) 26 return parent; 27 else 28 { 29 if((child = binomial_search(parent->child, key)) != NULL) 30 return child; 31 parent = parent->next; 32 } 33 } 34 35 return NULL; 36 } 37 38 /* 39 * 獲取二項堆中的最小根節點(*y) 40 * 41 * 參數說明: 42 * heap -- 二項堆 43 * prev_y -- [輸出參數]最小根節點y的前一個根節點 44 * y -- [輸出參數]最小根節點 45 */ 46 static void _binomial_minimum(BinomialHeap heap, 47 BinomialNode **prev_y, BinomialNode **y) 48 { 49 BinomialNode *x, *prev_x; // x是用來遍歷的當前節點 50 51 if (heap==NULL) 52 return ; 53 54 prev_x = heap; 55 x = heap->next; 56 *prev_y = NULL; 57 *y = heap; 58 // 找到最小節點 59 while (x != NULL) { 60 if (x->key < (*y)->key) { 61 *y = x; 62 *prev_y = prev_x; 63 } 64 prev_x = x; 65 x = x->next; 66 } 67 } 68 69 BinomialNode* binomial_minimum(BinomialHeap heap) 70 { 71 BinomialNode *prev_y, *y; 72 73 _binomial_minimum(heap, &prev_y, &y); 74 75 return y; 76 } 77 78 /* 79 * 合併兩個二項堆:將child合併到heap中 80 */ 81 static void binomial_link(BinomialHeap child, BinomialHeap heap) 82 { 83 child->parent = heap; 84 child->next = heap->child; 85 heap->child = child; 86 heap->degree++; 87 } 88 89 /* 90 * 將h1, h2中的根表合併成一個按度數遞增的鏈表,返回合併後的根節點 91 */ 92 static BinomialNode* binomial_merge(BinomialHeap h1, BinomialHeap h2) 93 { 94 BinomialNode* head = NULL; //heap爲指向新堆根結點 95 BinomialNode** pos = &head; 96 97 while (h1 && h2) 98 { 99 if (h1->degree < h2->degree) 100 { 101 *pos = h1; 102 h1 = h1->next; 103 } 104 else 105 { 106 *pos = h2; 107 h2 = h2->next; 108 } 109 pos = &(*pos)->next; 110 } 111 if (h1) 112 *pos = h1; 113 else 114 *pos = h2; 115 116 return head; 117 } 118 119 /* 120 * 合併二項堆:將h1, h2合併成一個堆,並返回合併後的堆 121 */ 122 BinomialNode* binomial_union(BinomialHeap h1, BinomialHeap h2) 123 { 124 BinomialNode *heap; 125 BinomialNode *prev_x, *x, *next_x; 126 127 // 將h1, h2中的根表合併成一個按度數遞增的鏈表heap 128 heap = binomial_merge(h1, h2); 129 if (heap == NULL) 130 return NULL; 131 132 prev_x = NULL; 133 x = heap; 134 next_x = x->next; 135 136 while (next_x != NULL) 137 { 138 if ( (x->degree != next_x->degree) 139 || ((next_x->next != NULL) && (next_x->degree == next_x->next->degree))) 140 { 141 // Case 1: x->degree != next_x->degree 142 // Case 2: x->degree == next_x->degree == next_x->next->degree 143 prev_x = x; 144 x = next_x; 145 } 146 else if (x->key <= next_x->key) 147 { 148 // Case 3: x->degree == next_x->degree != next_x->next->degree 149 // && x->key <= next_x->key 150 x->next = next_x->next; 151 binomial_link(next_x, x); 152 } 153 else 154 { 155 // Case 4: x->degree == next_x->degree != next_x->next->degree 156 // && x->key > next_x->key 157 if (prev_x == NULL) 158 { 159 heap = next_x; 160 } 161 else 162 { 163 prev_x->next = next_x; 164 } 165 binomial_link(x, next_x); 166 x = next_x; 167 } 168 next_x = x->next; 169 } 170 171 return heap; 172 } 173 174 /* 175 * 新建二項堆節點 176 */ 177 static BinomialNode* make_binomial_node(Type key) 178 { 179 BinomialNode* node; 180 181 node = (BinomialNode*)malloc(sizeof(BinomialNode)); 182 if (node==NULL) 183 { 184 printf("malloc BinomialNode failed!\n"); 185 return NULL; 186 } 187 188 node->key = key; 189 node->degree = 0; 190 node->parent = NULL; 191 node->child = NULL; 192 node->next = NULL; 193 194 return node; 195 } 196 197 /* 198 * 新建key對應的節點,並將其插入到二項堆中。 199 * 200 * 參數說明: 201 * heap -- 原始的二項樹。 202 * key -- 鍵值 203 * 返回值: 204 * 插入key以後的二項樹 205 */ 206 BinomialNode* binomial_insert(BinomialHeap heap, Type key) 207 { 208 BinomialNode* node; 209 210 if (binomial_search(heap, key) != NULL) 211 { 212 printf("insert failed: the key(%d) is existed already!\n", key); 213 return heap; 214 } 215 216 node = make_binomial_node(key); 217 if (node==NULL) 218 return heap; 219 220 return binomial_union(heap, node); 221 } 222 223 /* 224 * 反轉二項堆heap 225 */ 226 static BinomialNode* binomial_reverse(BinomialNode* heap) 227 { 228 BinomialNode* next; 229 BinomialNode* tail = NULL; 230 231 if (!heap) 232 return heap; 233 234 heap->parent = NULL; 235 while (heap->next) 236 { 237 next = heap->next; 238 heap->next = tail; 239 tail = heap; 240 heap = next; 241 heap->parent = NULL; 242 } 243 heap->next = tail; 244 245 return heap; 246 } 247 248 /* 249 * 移除最小節點,並返回移除節點後的二項堆 250 */ 251 BinomialNode* binomial_extract_minimum(BinomialHeap heap) 252 { 253 BinomialNode *y, *prev_y; // y是最小節點 254 255 if (heap==NULL) 256 return heap; 257 258 // 找到"最小節點根y"和"它的前一個根節點prev_y" 259 _binomial_minimum(heap, &prev_y, &y); 260 261 if (prev_y == NULL) // heap的根節點就是最小根節點 262 heap = heap->next; 263 else // heap的根節點不是最小根節點 264 prev_y->next = y->next; 265 266 // 反轉最小節點的左孩子,獲得最小堆child; 267 // 這樣,就使得最小節點所在二項樹的孩子們都脫離出來成爲一棵獨立的二項樹(不包括最小節點) 268 BinomialNode* child = binomial_reverse(y->child); 269 // 將"刪除最小節點的二項堆child"和"heap"進行合併。 270 heap = binomial_union(heap, child); 271 272 // 刪除最小節點 273 free(y); 274 275 return heap; 276 } 277 278 /* 279 * 減小關鍵字的值:將二項堆heap中的節點node的鍵值減少爲key。 280 */ 281 static void binomial_decrease_key(BinomialHeap heap, BinomialNode *node, Type key) 282 { 283 if ((key >= node->key) || (binomial_search(heap, key) != NULL)) 284 { 285 printf("decrease failed: the new key(%d) is existed already, \ 286 or is no smaller than current key(%d)\n", key, node->key); 287 return ; 288 } 289 node->key = key; 290 291 BinomialNode *child, *parent; 292 child = node; 293 parent = node->parent; 294 while(parent != NULL && child->key < parent->key) 295 { 296 swap(parent->key, child->key); 297 child = parent; 298 parent = child->parent; 299 } 300 } 301 302 /* 303 * 增長關鍵字的值:將二項堆heap中的節點node的鍵值增長爲key。 304 */ 305 static void binomial_increase_key(BinomialHeap heap, BinomialNode *node, Type key) 306 { 307 if ((key <= node->key) || (binomial_search(heap, key) != NULL)) 308 { 309 printf("increase failed: the new key(%d) is existed already, \ 310 or is no greater than current key(%d)\n", key, node->key); 311 return ; 312 } 313 node->key = key; 314 315 BinomialNode *cur, *child, *least; 316 cur = node; 317 child = cur->child; 318 while (child != NULL) 319 { 320 if(cur->key > child->key) 321 { 322 // 若是"當前節點" < "它的左孩子", 323 // 則在"它的孩子中(左孩子 和 左孩子的兄弟)"中,找出最小的節點; 324 // 而後將"最小節點的值" 和 "當前節點的值"進行互換 325 least = child; 326 while(child->next != NULL) 327 { 328 if (least->key > child->next->key) 329 { 330 least = child->next; 331 } 332 child = child->next; 333 } 334 // 交換最小節點和當前節點的值 335 swap(least->key, cur->key); 336 337 // 交換數據以後,再對"原最小節點"進行調整,使它知足最小堆的性質:父節點 <= 子節點 338 cur = least; 339 child = cur->child; 340 } 341 else 342 { 343 child = child->next; 344 } 345 } 346 } 347 348 /* 349 * 更新二項堆heap的節點node的鍵值爲key 350 */ 351 static void binomial_update_key(BinomialHeap heap, BinomialNode* node, Type key) 352 { 353 if (node == NULL) 354 return ; 355 356 if(key < node->key) 357 binomial_decrease_key(heap, node, key); 358 else if(key > node->key) 359 binomial_increase_key(heap, node, key); 360 else 361 printf("No need to update!!!\n"); 362 } 363 364 /* 365 * 將二項堆heap的鍵值oldkey更新爲newkey 366 */ 367 void binomial_update(BinomialHeap heap, Type oldkey, Type newkey) 368 { 369 BinomialNode *node; 370 371 node = binomial_search(heap, oldkey); 372 if (node != NULL) 373 binomial_update_key(heap, node, newkey); 374 } 375 376 /* 377 * 刪除節點:刪除鍵值爲key的節點,並返回刪除節點後的二項樹 378 */ 379 BinomialNode* binomial_delete(BinomialHeap heap, Type key) 380 { 381 BinomialNode *node; 382 BinomialNode *parent, *prev, *pos; 383 384 if (heap==NULL) 385 return heap; 386 387 // 查找鍵值爲key的節點 388 if ((node = binomial_search(heap, key)) == NULL) 389 return heap; 390 391 // 將被刪除的節點的數據數據上移到它所在的二項樹的根節點 392 parent = node->parent; 393 while (parent != NULL) 394 { 395 // 交換數據 396 swap(node->key, parent->key); 397 // 下一個父節點 398 node = parent; 399 parent = node->parent; 400 } 401 402 // 找到node的前一個根節點(prev) 403 prev = NULL; 404 pos = heap; 405 while (pos != node) 406 { 407 prev = pos; 408 pos = pos->next; 409 } 410 // 移除node節點 411 if (prev) 412 prev->next = node->next; 413 else 414 heap = node->next; 415 416 heap = binomial_union(heap, binomial_reverse(node->child)); 417 418 free(node); 419 420 return heap; 421 } 422 423 /* 424 * 打印"二項堆" 425 * 426 * 參數說明: 427 * node -- 當前節點 428 * prev -- 當前節點的前一個節點(父節點or兄弟節點) 429 * direction -- 1,表示當前節點是一個左孩子; 430 * 2,表示當前節點是一個兄弟節點。 431 */ 432 static void _binomial_print(BinomialNode *node, BinomialNode *prev, int direction) 433 { 434 while(node != NULL) 435 { 436 //printf("%2d \n", node->key); 437 if (direction == 1) 438 printf("\t%2d(%d) is %2d's child\n", node->key, node->degree, prev->key); 439 else 440 printf("\t%2d(%d) is %2d's next\n", node->key, node->degree, prev->key); 441 442 if (node->child != NULL) 443 _binomial_print(node->child, node, 1); 444 445 // 兄弟節點 446 prev = node; 447 node = node->next; 448 direction = 2; 449 } 450 } 451 452 void binomial_print(BinomialHeap heap) 453 { 454 if (heap == NULL) 455 return ; 456 457 BinomialNode *p = heap; 458 printf("== 二項堆( "); 459 while (p != NULL) 460 { 461 printf("B%d ", p->degree); 462 p = p->next; 463 } 464 printf(")的詳細信息:\n"); 465 466 int i=0; 467 while (heap != NULL) 468 { 469 i++; 470 printf("%d. 二項樹B%d: \n", i, heap->degree); 471 printf("\t%2d(%d) is root\n", heap->key, heap->degree); 472 473 _binomial_print(heap->child, heap, 1); 474 heap = heap->next; 475 } 476 printf("\n"); 477 }
二項堆的測試程序(main.c)
1 /** 2 * C語言實現的二項堆 3 * 4 * @author skywang 5 * @date 2014/04/01 6 */ 7 8 #include <stdio.h> 9 #include "binomial_heap.h" 10 11 #define DEBUG 1 12 13 #if DEBUG 14 #define log(x, ...) printf(x, __VA_ARGS__) 15 #else 16 #define log(x, ...) 17 #endif 18 19 #define LENGTH(a) ( (sizeof(a)) / (sizeof(a[0])) ) 20 21 // 共7個 = 1+2+4 22 int a[] = {12, 7, 25, 15, 28, 23 33, 41}; 24 // 共13個 = 1+4+8 25 int b[] = {18, 35, 20, 42, 9, 26 31, 23, 6, 48, 11, 27 24, 52, 13 }; 28 // 驗證"二項堆的插入操做" 29 void test_insert() 30 { 31 int i; 32 int alen=LENGTH(a); 33 BinomialHeap ha=NULL; 34 35 // 二項堆ha 36 printf("== 二項堆(ha)中依次添加: "); 37 for(i=0; i<alen; i++) 38 { 39 printf("%d ", a[i]); 40 ha = binomial_insert(ha, a[i]); 41 } 42 printf("\n"); 43 // 打印二項堆ha 44 printf("== 二項堆(ha)的詳細信息: \n"); 45 binomial_print(ha); 46 } 47 48 // 驗證"二項堆的合併操做" 49 void test_union() 50 { 51 int i; 52 int alen=LENGTH(a); 53 int blen=LENGTH(b); 54 BinomialHeap ha,hb; 55 56 ha=hb=NULL; 57 58 // 二項堆ha 59 printf("== 二項堆(ha)中依次添加: "); 60 for(i=0; i<alen; i++) 61 { 62 printf("%d ", a[i]); 63 ha = binomial_insert(ha, a[i]); 64 } 65 printf("\n"); 66 printf("== 二項堆(ha)的詳細信息: \n"); 67 binomial_print(ha); // 打印二項堆ha 68 69 // 二項堆hb 70 printf("== 二項堆(hb)中依次添加: "); 71 for(i=0; i<blen; i++) 72 { 73 printf("%d ", b[i]); 74 hb = binomial_insert(hb, b[i]); 75 } 76 printf("\n"); 77 printf("== 二項堆(hb)的詳細信息: \n"); 78 binomial_print(hb); // 打印二項堆hb 79 80 // 將"二項堆hb"合併到"二項堆ha"中。 81 ha = binomial_union(ha, hb); 82 printf("== 合併ha和hb後的詳細信息:\n"); 83 binomial_print(ha); // 打印二項堆ha的詳細信息 84 } 85 86 // 驗證"二項堆的刪除操做" 87 void test_delete() 88 { 89 int i; 90 int blen=LENGTH(b); 91 BinomialHeap hb=NULL; 92 93 // 二項堆hb 94 printf("== 二項堆(hb)中依次添加: "); 95 for(i=0; i<blen; i++) 96 { 97 printf("%d ", b[i]); 98 hb = binomial_insert(hb, b[i]); 99 } 100 printf("\n"); 101 printf("== 二項堆(hb)的詳細信息: \n"); 102 binomial_print(hb); // 打印二項堆hb 103 104 // 刪除二項堆hb中的節點 105 i = 20; 106 hb = binomial_delete(hb, i); 107 printf("== 刪除節點%d後的詳細信息: \n", i); 108 binomial_print(hb); // 打印二項堆hb 109 } 110 111 // 驗證"二項堆的更新(減小)操做" 112 void test_decrease() 113 { 114 int i; 115 int blen=LENGTH(b); 116 BinomialHeap hb=NULL; 117 118 // 二項堆hb 119 printf("== 二項堆(hb)中依次添加: "); 120 for(i=0; i<blen; i++) 121 { 122 printf("%d ", b[i]); 123 hb = binomial_insert(hb, b[i]); 124 } 125 printf("\n"); 126 printf("== 二項堆(hb)的詳細信息: \n"); 127 binomial_print(hb); // 打印二項堆hb 128 129 // 將節點20更新爲2 130 binomial_update(hb, 20, 2); 131 printf("== 更新節點20->2後的詳細信息: \n"); 132 binomial_print(hb); // 打印二項堆hb 133 } 134 135 // 驗證"二項堆的更新(增長)操做" 136 void test_increase() 137 { 138 int i; 139 int blen=LENGTH(b); 140 BinomialHeap hb=NULL; 141 142 // 二項堆hb 143 printf("== 二項堆(hb)中依次添加: "); 144 for(i=0; i<blen; i++) 145 { 146 printf("%d ", b[i]); 147 hb = binomial_insert(hb, b[i]); 148 } 149 printf("\n"); 150 printf("== 二項堆(hb)的詳細信息: \n"); 151 binomial_print(hb); // 打印二項堆hb 152 153 // 將節點6更新爲20 154 binomial_update(hb, 6, 60); 155 printf("== 更新節點6->60後的詳細信息: \n"); 156 binomial_print(hb); // 打印二項堆hb 157 } 158 159 160 void main() 161 { 162 // 1. 驗證"二項堆的插入操做" 163 test_insert(); 164 // 2. 驗證"二項堆的合併操做" 165 //test_union(); 166 // 3. 驗證"二項堆的刪除操做" 167 //test_delete(); 168 // 4. 驗證"二項堆的更新(減小)操做" 169 //test_decrease(); 170 // 5. 驗證"二項堆的更新(增長)操做" 171 //test_increase(); 172 }
二項堆的測試程序包括了五部分,分別是"插入"、"刪除"、"增長"、"減小"、"合併"這5種功能的測試代碼。默認是運行的"插入"功能代碼,你能夠根據本身的須要來對相應的功能進行驗證!
下面是插入功能運行結果:
== 二項堆(ha)中依次添加: 12 7 25 15 28 33 41 == 二項堆(ha)的詳細信息: == 二項堆( B0 B1 B2 )的詳細信息: 1. 二項樹B0: 41(0) is root 2. 二項樹B1: 28(1) is root 33(0) is 28's child 3. 二項樹B2: 7(2) is root 15(1) is 7's child 25(0) is 15's child 12(0) is 15's next