深度解析(十三)二项堆

二项堆(一)之 图文解析 和 C语言的实现

概要

本章介绍二项堆,它和之前所讲的堆(二叉堆左倾堆斜堆)一样,也是用于实现优先队列的。和以往一样,本文会先对二项堆的理论知识进行简单介绍,然后给出C语言的实现。后续再分别给出C++和Java版本的实现;实现的语言虽不同,但是原理一样,选择其中之一进行了解即可。若文章有错误或不足的地方,请不吝指出!

目录
1. 二项树的介绍
2. 二项堆的介绍
3. 二项堆的基本操作
4. 二项堆的C实现(完整源码)
5. 二项堆的C测试程序

转载请注明出处:http://www.cnblogs.com/skywang12345/p/3655900.html



更多内容:数据结构与算法系列 目录

(01) 二项堆(一)之 图文解析 和 C语言的实现
(02) 二项堆(二)之 C++的实现
(03) 二项堆(二)之 Java的实现

二项树的介绍

二项树的定义

二项堆是二项树的集合。在了解二项堆之前,先对二项树进行介绍。

二项树是一种递归定义的有序树。它的递归定义如下:
(01) 二项树B0只有一个结点;
(02) 二项树Bk由两棵二项树B(k-1)组成的,其中一棵树是另一棵树根的最左孩子。
如下图所示:

上图的B0、B1、B2、B3、B4都是二项树。对比前面提到的二项树的定义:B0只有一个节点,B1由两个B0所组成,B2由两个B1所组成,B3由两个B2所组成,B4由两个B3所组成;而且,当两颗相同的二项树组成另一棵树时,其中一棵树是另一棵树的最左孩子。

二项树的性质

二项树有以下性质:
[性质一] 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)!

二项堆的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

二项堆的实现文件(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 }

二项堆的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

原文地址:https://www.cnblogs.com/alantu2018/p/8465219.html

时间: 2024-10-29 00:56:11

深度解析(十三)二项堆的相关文章

二项堆(一)之 图文解析 和 C语言的实现

概要 本章介绍二项堆,它和之前所讲的堆(二叉堆.左倾堆.斜堆)一样,也是用于实现优先队列的.和以往一样,本文会先对二项堆的理论知识进行简单介绍,然后给出C语言的实现.后续再分别给出C++和Java版本的实现:实现的语言虽不同,但是原理一样,选择其中之一进行了解即可.若文章有错误或不足的地方,请不吝指出! 目录1. 二项树的介绍2. 二项堆的介绍3. 二项堆的基本操作4. 二项堆的C实现(完整源码)5. 二项堆的C测试程序 转载请注明出处:http://www.cnblogs.com/skywan

12、【堆】二项堆

一.二项树的介绍 二项树的定义 二项堆是二项树的集合.在了解二项堆之前,先对二项树进行介绍. 二项树是一种递归定义的有序树.它的递归定义如下: (1) 二项树B0只有一个结点: (2) 二项树Bk由两棵二项树B(k-1)组成的,其中一棵树是另一棵树根的最左孩子. 如下图所示: 上图的B0.B1.B2.B3.B4都是二项树.对比前面提到的二项树的定义:B0只有一个节点,B1由两个B0所组成,B2由两个B1所组成,B3由两个B2所组成,B4由两个B3所组成:而且,当两颗相同的二项树组成另一棵树时,其

数据结构——二项堆

二项树的介绍 二项树的定义 二项堆是二项树的集合.在了解二项堆之前,先对二项树进行介绍. 二项树是一种递归定义的有序树.它的递归定义如下:(01) 二项树B0只有一个结点:(02) 二项树Bk由两棵二项树B(k-1)组成的,其中一棵树是另一棵树根的最左孩子.如下图所示: 上图的B0.B1.B2.B3.B4都是二项树.对比前面提到的二项树的定义:B0只有一个节点,B1由两个B0所组成,B2由两个B1所组成,B3由两个B2所组成,B4由两个B3所组成:而且,当两颗相同的二项树组成另一棵树时,其中一棵

二项堆

在计算机科学中,二项堆(Binomial Heap)是一种堆结构.与二叉堆(Binary Heap)相比,其优势是可以快速合并两个堆,因此它属于可合并堆(Mergeable Heap)数据结构的一种. 可合并堆通常支持下面几种操作: Make-Heap():创建并返回一个不包含任何元素的新堆. Insert(H, x):将节点 x 插入到堆 H 中. Minimum(H):返回堆 H 中的最小关键字. Extract-Min(H):将堆 H 中包含最小关键字的节点删除. Union(H1, H2

笔试算法题(46):简介 - 二叉堆 &amp; 二项树 &amp; 二项堆 &amp; 斐波那契堆

二叉堆(Binary Heap) 二叉堆是完全二叉树(或者近似完全二叉树):其满足堆的特性:父节点的值>=(<=)任何一个子节点的键值,并且每个左子树或者右子树都是一 个二叉堆(最小堆或者最大堆):一般使用数组构建二叉堆,对于array[i]而言,其左子节点为array[2*i],其右子节点为 array[2*i+1]:二叉堆支持插入,删除,查找最大(最小)键值的操作,但是合并二叉堆的复杂度较高,时间复杂度为O(N):但是二项堆或者斐波 那契堆则仅需要O(logN): 二项树(Binomial

[硕.Love Python] BinomialHeap(B堆 & 二项堆)

class Node(object):     def __init__(self, data):         self.data = data         self.child = None         self.left = None         self.right = None         self.degree = 0     def __str__(self):         return str(self.data)     __repr__ = __str_

算法导论 二项堆

可以合并的堆 二项堆 先看看堆得操作运行时间的对比 对于使用这种数据结构有个概念 二项堆在union操作合并上有优势 二项堆由二项树组成 我们先看看二项树的定义 因为性质3中的系数就是二项式分布的系数,所以二项树名由此得来 B0 有2º = 1个节点 B? 有2¹=2个节点 且根节点比子节点小 B? 有2²=4个节点 且根节点比子节点小 B? 有2³=8个节点 且根节点比子节点小 二项堆则按照以下规则有二项树组成 由图 因为二项堆都是最小堆有序的,最小节点必定在跟节点中,寻找最小关键字就比较简单

Java并发之synchronized关键字深度解析(二)

前言 本文继续[Java并发之synchronized关键字深度解析(一)]一文而来,着重介绍synchronized几种锁的特性. 一.对象头结构及锁状态标识 synchronized关键字是如何实现的给对象加锁?首先我们要了解一下java中对象的组成.java中的对象由3部分组成,第一部分是对象头,第二部分是实例数据,第三部分是对齐填充. 对齐填充:jvm规定对象的起始内存地址必须是8字节的整数倍,如果不够的话就用占位符来填充,此部分占位符就是对齐填充: 实例数据:实例数据是对象存储的真正有

优先队列(堆)&#183;二项队列

目录 一. 定义 二. 结构 三. 操作 3.1. 合并 3.1. 删除最小值(deleteMin) 四. 二项队列的实现 代码地址 一. 定义 ? 我们知道,左式堆每次操作的时间界是\(O(logN)\).二项队列支持合并.插入.删除最小值,每次插入的平均时间为常数时间,而最坏时间是\(O(logN)\). ? 二项队列: 不是一棵堆序的树,而是堆序的树的集合,成为森林. 森林的每棵树都是二项树(binomial tree). 每个高度上至多存在一棵二项树. 二. 结构 ? 结构图解: ? 高