二項堆(一)之 圖文解析 和 C語言的實現

 

概要

本章介紹二項堆,它和以前所講的堆(二叉堆左傾堆斜堆)同樣,也是用於實現優先隊列的。和以往同樣,本文會先對二項堆的理論知識進行簡單介紹,而後給出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 }
View Code

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 }
View Code

合併操做代碼(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 }
View Code

刪除操做代碼(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)!

 

二項堆的C實現(完整源碼)

二項堆的頭文件(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
View Code

二項堆的實現文件(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 }
View Code

二項堆的測試程序(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 }
View Code

 

二項堆的C測試程序

二項堆的測試程序包括了五部分,分別是"插入"、"刪除"、"增長"、"減小"、"合併"這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
相關文章
相關標籤/搜索