• 二项堆(一)之 图文解析 和 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 }
    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!
    ", 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!!!
    ");
    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)
    ", 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)
    ", 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!
    ");
    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!
    ", 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)
    ", 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)
    ", 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!!!
    ");
    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 
    ", node->key);
    437         if (direction == 1)
    438             printf("	%2d(%d) is %2d's child
    ", node->key, node->degree, prev->key);
    439         else
    440             printf("	%2d(%d) is %2d's next
    ", 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(")的详细信息:
    ");
    465 
    466     int i=0;
    467     while (heap != NULL) 
    468     {
    469         i++;
    470         printf("%d. 二项树B%d: 
    ", i, heap->degree);
    471         printf("	%2d(%d) is root
    ", heap->key, heap->degree);
    472 
    473         _binomial_print(heap->child, heap, 1);
    474         heap = heap->next;
    475     }
    476     printf("
    ");
    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("
    ");
     43     // 打印二项堆ha
     44     printf("== 二项堆(ha)的详细信息: 
    ");
     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("
    ");
     66     printf("== 二项堆(ha)的详细信息: 
    ");
     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("
    ");
     77     printf("== 二项堆(hb)的详细信息: 
    ");
     78     binomial_print(hb); // 打印二项堆hb
     79 
     80     // 将"二项堆hb"合并到"二项堆ha"中。
     81     ha = binomial_union(ha, hb);
     82     printf("== 合并ha和hb后的详细信息:
    ");
     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("
    ");
    101     printf("== 二项堆(hb)的详细信息: 
    ");
    102     binomial_print(hb); // 打印二项堆hb
    103 
    104     // 删除二项堆hb中的节点
    105     i = 20;
    106     hb = binomial_delete(hb, i);
    107     printf("== 删除节点%d后的详细信息: 
    ", 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("
    ");
    126     printf("== 二项堆(hb)的详细信息: 
    ");
    127     binomial_print(hb); // 打印二项堆hb
    128 
    129     // 将节点20更新为2
    130     binomial_update(hb, 20, 2);
    131     printf("== 更新节点20->2后的详细信息: 
    ");
    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("
    ");
    150     printf("== 二项堆(hb)的详细信息: 
    ");
    151     binomial_print(hb); // 打印二项堆hb
    152 
    153     // 将节点6更新为20
    154     binomial_update(hb, 6, 60);
    155     printf("== 更新节点6->60后的详细信息: 
    ");
    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
  • 相关阅读:
    [反汇编练习] 160个CrackMe之002
    [反汇编练习]160个CrackMe之001
    Leap Motion 开发笔记
    HTMLayout界面CSSS样式解析笔记
    DbgPrint输出格式 Unicodestring
    WinDbg调试命令汇总
    WDF模型驱动程序开发
    [Sciter系列] MFC下的Sciter–5.Sciter中GUI线程研究
    剑指offer第二版-4.二维数组中的查找
    剑指offer第二版-3.数组中重复的数
  • 原文地址:https://www.cnblogs.com/skywang12345/p/3655900.html
Copyright © 2020-2023  润新知