深度解析(十四)斐波那契堆

Posted alantu2018

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度解析(十四)斐波那契堆相关的知识,希望对你有一定的参考价值。

斐波那契堆(一)之 图文解析 和 C语言的实现

概要

本章介绍斐波那契堆。和以往一样,本文会先对斐波那契堆的理论知识进行简单介绍,然后给出C语言的实现。后续再分别给出C++和Java版本的实现;实现的语言虽不同,但是原理如出一辙,选择其中之一进行了解即可。若文章有错误或不足的地方,请不吝指出!

目录
1. 斐波那契堆的介绍
2. 斐波那契堆的基本操作
3. 斐波那契堆的C实现(完整源码)
4. 斐波那契堆的C测试程序

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


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

(01) 斐波那契堆(一)之 图文解析 和 C语言的实现 
(02) 斐波那契堆(二)之 C++的实现 
(03) 斐波那契堆(三)之 Java的实现

 

斐波那契堆的介绍

斐波那契堆(Fibonacci heap)是堆中一种,它和二项堆一样,也是一种可合并堆;可用于实现合并优先队列。斐波那契堆比二项堆具有更好的平摊分析性能,它的合并操作的时间复杂度是O(1)。
与二项堆一样,它也是由一组堆最小有序树组成,并且是一种可合并堆。
与二项堆不同的是,斐波那契堆中的树不一定是二项树;而且二项堆中的树是有序排列的,但是斐波那契堆中的树都是有根而无序的。

技术分享图片

 

斐波那契堆的基本操作

1. 基本定义

技术分享图片
typedef int Type;

typedef struct _FibonacciNode
{
    Type   key;                     // 关键字(键值)
    int degree;                     // 度数
    struct _FibonacciNode *left;    // 左兄弟
    struct _FibonacciNode *right;   // 右兄弟
    struct _FibonacciNode *child;   // 第一个孩子节点
    struct _FibonacciNode *parent;  // 父节点
    int marked;                     //是否被删除第1个孩子(1表示删除,0表示未删除)
}FibonacciNode, FibNode;
技术分享图片

FibNode是斐波那契堆的节点类,它包含的信息较多。key是用于比较节点大小的,degree是记录节点的度,left和right分别是指向节点的左右兄弟,child是节点的第一个孩子,parent是节点的父节点,marked是记录该节点是否被删除第1个孩子(marked在删除节点时有用)。

 

typedef struct _FibonacciHeap{
    int   keyNum;                   // 堆中节点的总数
    int   maxDegree;                // 最大度
    struct _FibonacciNode *min;     // 最小节点(某个最小堆的根节点)
    struct _FibonacciNode **cons;   // 最大度的内存区域
}FibonacciHeap, FibHeap;

FibHeap是斐波那契堆对应的类。min是保存当前堆的最小节点,keyNum用于记录堆中节点的总数,maxDegree用于记录堆中最大度,而cons在删除节点时来暂时保存堆数据的临时空间。

 

下面看看斐波那契堆的内存结构图。

技术分享图片

从图中可以看出,斐波那契堆是由一组最小堆组成,这些最小堆的根节点组成了双向链表(后文称为"根链表");斐波那契堆中的最小节点就是"根链表中的最小节点"!

PS. 上面这幅图的结构和测试代码中的"基本信息"测试函数的结果是一致的;你可以通过测试程序来亲自验证!

 

2. 插入操作

插入操作非常简单:插入一个节点到堆中,直接将该节点插入到"根链表的min节点"之前即可;若被插入节点比"min节点"小,则更新"min节点"为被插入节点。

技术分享图片

上面是插入操作的示意图。

斐波那契堆的根链表是"双向链表",这里将min节点看作双向联表的表头(后文也是如此)。在插入节点时,每次都是"将节点插入到min节点之前(即插入到双链表末尾)"。此外,对于根链表中最小堆都只有一个节点的情况,插入操作就很演化成双向链表的插入操作。

此外,插入操作示意图与测试程序中的"插入操作"相对应,感兴趣的可以亲自验证。

 

插入操作代码

技术分享图片
/*
 * 将"单个节点node"加入"链表root"之前
 *   a …… root
 *   a …… node …… root
 *
 * 注意: 此处node是单个节点,而root是双向链表
*/
static void fib_node_add(FibNode *node, FibNode *root)
{
    node->left        = root->left;
    root->left->right = node;
    node->right       = root;
    root->left        = node;
}

/*
 * 将节点node插入到斐波那契堆heap中
 */
static void fib_heap_insert_node(FibHeap *heap, FibNode *node)
{
    if (heap->keyNum == 0)
        heap->min = node;
    else
       {
        fib_node_add(node, heap->min);
        if (node->key < heap->min->key)
            heap->min = node;
    }
    heap->keyNum++;
}
技术分享图片

 

3. 合并操作

合并操作和插入操作的原理非常类似:将一个堆的根链表插入到另一个堆的根链表上即可。简单来说,就是将两个双链表拼接成一个双向链表。

技术分享图片

上面是合并操作的示意图。该操作示意图与测试程序中的"合并操作"相对应!

合并操作代码

技术分享图片
/*
 * 将双向链表b链接到双向链表a的后面
 *
 * 注意: 此处a和b都是双向链表
*/
static void fib_node_cat(FibNode *a, FibNode *b)
{
    FibNode *tmp;

    tmp            = a->right;
    a->right       = b->right;
    b->right->left = a;
    b->right       = tmp;
    tmp->left      = b;
}

/*
 * 将h1, h2合并成一个堆,并返回合并后的堆
 */
FibHeap* fib_heap_union(FibHeap *h1, FibHeap *h2)
{
    FibHeap *tmp;

    if (h1==NULL)
        return h2;
    if (h2==NULL)
        return h1;

    // 以h1为"母",将h2附加到h1上;下面是保证h1的度数大,尽可能的少操作。
    if(h2->maxDegree > h1->maxDegree)
    {
        tmp = h1;
        h1 = h2;
        h2 = tmp;
    }

    if((h1->min) == NULL)                // h1无"最小节点"
    {
        h1->min = h2->min;
        h1->keyNum = h2->keyNum;
        free(h2->cons);
        free(h2);
    }
    else if((h2->min) == NULL)           // h1有"最小节点" && h2无"最小节点"
    {
        free(h2->cons);
        free(h2);
    }                                   // h1有"最小节点" && h2有"最小节点"
    else
    {
        // 将"h2中根链表"添加到"h1"中
        fib_node_cat(h1->min, h2->min);
        if (h1->min->key > h2->min->key)
            h1->min = h2->min;
        h1->keyNum += h2->keyNum;
        free(h2->cons);
        free(h2);
    }

    return h1;
}
技术分享图片

 

4. 取出最小节点

抽取最小结点的操作是斐波那契堆中较复杂的操作。
(1)将要抽取最小结点的子树都直接串联在根表中;
(2)合并所有degree相等的树,直到没有相等的degree的树。

技术分享图片

上面是取出最小节点的示意图。图中应该写的非常明白了,若有疑问,看代码。

此外,该操作示意图与测试程序中的"删除最小节点"相对应!有兴趣的可以亲自验证。

取出最小节点代码

技术分享图片
/*
 * 移除最小节点,并返回移除节点后的斐波那契堆
 */
FibNode* _fib_heap_extract_min(FibHeap *heap)
{
    if (heap==NULL || heap->min==NULL)
        return NULL;

    FibNode *child = NULL;
    FibNode *min = heap->min;
    // 将min每一个儿子(儿子和儿子的兄弟)都添加到"斐波那契堆的根链表"中
    while (min->child != NULL)
    {
        child = min->child;
        fib_node_remove(child);
        if (child->right == child)
            min->child = NULL;
        else
            min->child = child->right;

        fib_node_add(child, heap->min);
        child->parent = NULL;
    }

    // 将min从根链表中移除
    fib_node_remove(min);
    // 若min是堆中唯一节点,则设置堆的最小节点为NULL;
    // 否则,设置堆的最小节点为一个非空节点(min->right),然后再进行调节。
    if (min->right == min)
        heap->min = NULL;
    else
    {
        heap->min = min->right;
        fib_heap_consolidate(heap);
    }
    heap->keyNum--;

    return min;
}
技术分享图片

其中fib_heap_consolidate(heap)的作用是合并斐波那契堆的根链表中相同度数的树,它的相关代码如下:

 

 1 /*
 2  * 将node链接到root根结点
 3  */
 4 static void fib_heap_link(FibHeap * heap, FibNode * node, FibNode *root)
 5 {
 6     // 将node从双链表中移除
 7     fib_node_remove(node);
 8     // 将node设为root的孩子
 9     if (root->child == NULL)
10         root->child = node;
11     else
12         fib_node_add(node, root->child);
13 
14     node->parent = root;
15     root->degree++;
16     node->marked = 0;
17 }
18  
19 /* 
20  * 创建fib_heap_consolidate所需空间
21  */
22 static void fib_heap_cons_make(FibHeap * heap)
23 {
24     int old = heap->maxDegree;
25 
26     // 计算log2(x),"+1"意味着向上取整!
27     // ex. log2(13) = 3,向上取整为3+1=4。
28     heap->maxDegree = LOG2(heap->keyNum) + 1;
29 
30     // 如果原本空间不够,则再次分配内存
31     if (old >= heap->maxDegree)
32         return ;
33 
34     // 因为度为heap->maxDegree可能被合并,所以要maxDegree+1
35     heap->cons = (FibNode **)realloc(heap->cons, 
36             sizeof(FibHeap *) * (heap->maxDegree + 1));
37 }
38 
39 /* 
40  * 合并斐波那契堆的根链表中左右相同度数的树
41  */
42 static void fib_heap_consolidate(FibHeap *heap)
43 {
44     int i, d, D;
45     FibNode *x, *y, *tmp;
46 
47     fib_heap_cons_make(heap);//开辟哈希所用空间
48     D = heap->maxDegree + 1;
49 
50     for (i = 0; i < D; i++)
51         heap->cons[i] = NULL;
52  
53     // 合并相同度的根节点,使每个度数的树唯一
54     while (heap->min != NULL)
55     {
56         x = fib_heap_remove_min(heap);    // 取出堆中的最小树(最小节点所在的树)
57         d = x->degree;                    // 获取最小树的度数
58         // heap->cons[d] != NULL,意味着有两棵树(x和y)的"度数"相同。
59         while (heap->cons[d] != NULL)
60         {
61             y = heap->cons[d];            // y是"与x的度数相同的树" 
62             if (x->key > y->key)        // 保证x的键值比y小
63             {
64                 tmp = x;
65                 x = y;
66                 y = tmp;
67 
68             }
69             fib_heap_link(heap, y, x);    // 将y链接到x中
70             heap->cons[d] = NULL;
71             d++;
72         }
73         heap->cons[d] = x;
74     }
75     heap->min = NULL;
76  
77     // 将heap->cons中的结点重新加到根表中
78     for (i=0; i<D; i++)
79     {
80         if (heap->cons[i] != NULL)
81         {
82             if (heap->min == NULL)
83                 heap->min = heap->cons[i];
84             else
85             {
86                 fib_node_add(heap->cons[i], heap->min);
87                 if ((heap->cons[i])->key < heap->min->key)
88                     heap->min = heap->cons[i];
89             }
90         }
91     }
92 }

 

 

5. 减小节点值

减少斐波那契堆中的节点的键值,这个操作的难点是:如果减少节点后破坏了"最小堆"性质,如何去维护呢?下面对一般性情况进行分析。
(1) 首先,将"被减小节点"从"它所在的最小堆"剥离出来;然后将"该节点"关联到"根链表"中。 倘若被减小的节点不是单独一个节点,而是包含子树的树根。则是将以"被减小节点"为根的子树从"最小堆"中剥离出来,然后将该树关联到根链表中。
(2) 接着,对"被减少节点"的原父节点进行"级联剪切"。所谓"级联剪切",就是在被减小节点破坏了最小堆性质,并被切下来之后;再从"它的父节点"进行递归级联剪切操作。
      而级联操作的具体动作则是:若父节点(被减小节点的父节点)的marked标记为false,则将其设为true,然后退出。
                                                          否则,将父节点从最小堆中切下来(方式和"切被减小节点的方式"一样);然后递归对祖父节点进行"级联剪切"。
      marked标记的作用就是用来标记"该节点的子节点是否有被删除过",它的作用是来实现级联剪切。而级联剪切的真正目的是为了防止"最小堆"由二叉树演化成链表。
(3) 最后,别忘了对根链表的最小节点进行更新。

技术分享图片

上面是减小节点值的示意图。该操作示意图与测试程序中的"减小节点"相对应!

减小节点值的代码

技术分享图片
/* 
 * 将斐波那契堆heap中节点node的值减少为key
 */
static void fib_heap_decrease(FibHeap *heap, FibNode *node, Type key)
{
    FibNode *parent;

    if (heap==NULL || heap->min==NULL ||node==NULL) 
        return ;

    if ( key>=node->key)
    {
        printf("decrease failed: the new key(%d) is no smaller than current key(%d)\\n", key, node->key);
        return ;
    }

    node->key = key;
    parent = node->parent;
    if (parent!=NULL && node->key < parent->key)
    {
        // 将node从父节点parent中剥离出来,并将node添加到根链表中
        fib_heap_cut(heap, node, parent);
        fib_heap_cascading_cut(heap, parent);
    }

    // 更新最小节点
    if (node->key < heap->min->key)
        heap->min = node;
}
技术分享图片

其中,fib_heap_cut()和fib_heap_cascading_cut()的相关代码如下:

 1 /* 
 2  * 将node从父节点parent的子链接中剥离出来,
 3  * 并使node成为"堆的根链表"中的一员。
 4  */
 5 static void fib_heap_cut(FibHeap *heap, FibNode *node, FibNode *parent)
 6 {
 7     fib_node_remove(node);
 8     renew_degree(parent, node->degree);
 9     // node没有兄弟
10     if (node == node->right) 
11         parent->child = NULL;
12     else 
13         parent->child = node->right;
14 
15     node->parent = NULL;
16     node->left = node->right = node;
17     node->marked = 0;
18     // 将"node所在树"添加到"根链表"中
19     fib_node_add(node, heap->min);
20 }
21 
22 /* 
23  * 对节点node进行"级联剪切"
24  *
25  * 级联剪切:如果减小后的结点破坏了最小堆性质,
26  *     则把它切下来(即从所在双向链表中删除,并将
27  *     其插入到由最小树根节点形成的双向链表中),
28  *     然后再从"被切节点的父节点"到所在树根节点递归执行级联剪枝
29  */
30 static void fib_heap_cascading_cut(FibHeap *heap, FibNode *node) 
31 {
32     FibNode *parent = node->parent;
33     if (parent != NULL)
34         return ;
35 
36     if (node->marked == 0) 
37         node->marked = 1;
38     else
39     {
40         fib_heap_cut(heap, node, parent);
41         fib_heap_cascading_cut(heap, parent);
42     }
43 }

 

6. 增加节点值

增加节点值和减少节点值类似,这个操作的难点也是如何维护"最小堆"性质。思路如下:
(1) 将"被增加节点"的"左孩子和左孩子的所有兄弟"都链接到根链表中。
(2) 接下来,把"被增加节点"添加到根链表;但是别忘了对其进行级联剪切。

技术分享图片

上面是增加节点值的示意图。该操作示意图与测试程序中的"增大节点"相对应!

增加节点值的代码

技术分享图片
/* 
 * 将斐波那契堆heap中节点node的值增加为key
 */
static void fib_heap_increase(FibHeap *heap, FibNode *node, Type key)
{
    FibNode *child, *parent, *right;

    if (heap==NULL || heap->min==NULL ||node==NULL) 
        return ;

    if (key <= node->key)
    {
        printf("increase failed: the new key(%d) is no greater than current key(%d)\\n", key, node->key);
        return ;
    }

    // 将node每一个儿子(不包括孙子,重孙,...)都添加到"斐波那契堆的根链表"中
    while (node->child != NULL)
    {
        child = node->child;
        fib_node_remove(child);               // 将child从node的子链表中删除
        if (child->right == child)
            node->child = NULL;
        else
            node->child = child->right;

        fib_node_add(child, heap->min);       // 将child添加到根链表中
        child->parent = NULL;
    }
    node->degree = 0;
    node->key = key;

    // 如果node不在根链表中,
    //     则将node从父节点parent的子链接中剥离出来,
    //     并使node成为"堆的根链表"中的一员,
    //     然后进行"级联剪切"
    // 否则,则判断是否需要更新堆的最小节点
    parent = node->parent;
    if(parent != NULL)
    {
        fib_heap_cut(heap, node, parent);
        fib_heap_cascading_cut(heap, parent);
    }
    else if(heap->min == node)
    {
        right = node->right;
        while(right != node)
        {
            if(node->key > right->key)
                heap->min = right;
            right = right->right;
        }
    }
}
技术分享图片

 

7. 删除节点

删除节点,本文采用了操作是:"取出最小节点"和"减小节点值"的组合。
(1) 先将被删除节点的键值减少。减少后的值要比"原最小节点的值"即可。
(2) 接着,取出最小节点即可。

删除节点值的代码

技术分享图片
/*
 * 删除结点node
 */
static void _fib_heap_delete(FibHeap *heap, FibNode *node)
{
    Type min = heap->min->key;
    fib_heap_decrease(heap, node, min-1);
    _fib_heap_extract_min(heap);
    free(node);
}
技术分享图片


注意:关于斐波那契堆的"更新"、"打印"、"销毁"等接口就不再单独介绍了。后文的源码中有给出它们的实现代码,Please RTFSC(Read The Fucking Source Code)!

 

斐波那契堆的C实现(完整源码)

斐波那契堆的头文件(fibonacci_heap.h)

 1 #ifndef _FIBONACCI_HEAP_H_
 2 #define _FIBONACCI_HEAP_H_
 3 
 4 typedef int Type;
 5 
 6 typedef struct _FibonacciNode
 7 {
 8     Type   key;                        // 关键字(键值)
 9     int degree;                        // 度数
10     struct _FibonacciNode *left;    // 左兄弟
11     struct _FibonacciNode *right;    // 右兄弟
12     struct _FibonacciNode *child;    // 第一个孩子节点
13     struct _FibonacciNode *parent;    // 父节点
14     int marked;                       //是否被删除第1个孩子(1表示删除,0表示未删除)
15 }FibonacciNode, FibNode;
16 
17 typedef struct _FibonacciHeap{
18     int   keyNum;                    // 堆中节点的总数
19     int   maxDegree;                // 最大度
20     struct _FibonacciNode *min;        // 最小节点(某个最小堆的根节点)
21     struct _FibonacciNode **cons;    // 最大度的内存区域
22 }FibonacciHeap, FibHeap;
23 
24 // 创建Fibonacci堆
25 FibHeap* fib_heap_make();
26 // 新建键值为key的节点,并将其插入到斐波那契堆中
27 void fib_heap_insert_key(FibHeap *heap, Type key);
28 // 删除键值为key的结点
29 void fib_heap_delete(FibHeap *heap, Type key);
30 // 移除最小节点
31 void fib_heap_extract_min(FibHeap *heap);
32 // 更新heap的中的oldkey为newkey
33 void fib_heap_update(FibHeap *heap, Type oldkey, Type newkey);
34 // 将h1, h2合并成一个堆,并返回合并后的堆
35 FibHeap* fib_heap_union(FibHeap *h1, FibHeap *h2);
36 // 在斐波那契堆heap中是否存在键值为key的节点;存在返回1,否则返回0。
37 int fib_heap_contains(FibHeap *heap, Type key);
38 // 获取最小节点对应的值(保存在pkey中);成功返回1,失败返回0。
39 int fib_heap_get_min(FibHeap *heap, Type *pkey);
40 // 销毁斐波那契堆
41 void fib_heap_destroy(FibHeap *heap);
42 // 打印"斐波那契堆"
43 void fib_print(FibHeap *heap);
44 
45 #endif

斐波那契堆的实现文件(fibonacci_heap.c)

 

  1 /**
  2  * C语言实现的斐波那契堆
  3  *
  4  * @author skywang
  5  * @date 2014/04/05
  6  */
  7 
  8 #include <stdio.h>
  9 #include <stdlib.h>
 10 #include <math.h>
 11 #include <float.h>
 12 #include "fibonacci_heap.h"
 13 
 14 #if 0
 15 #define LOG2(x) ({  16         unsigned int _i = 0;  17         __asm__("bsr %1, %0" : "=r" (_i) : "r" ((x)));  18         _i; })
 19 #else   // 注意:通过gcc编译时,要添加 -lm 选项。
 20 #define LOG2(x) ((log((double)(x))) / (log(2.0)))
 21 #endif
 22 
 23 
 24 static FibNode *fib_heap_search(FibHeap *heap, Type key);
 25  
 26 /* 
 27  * 将node从双链表移除
 28  */
 29 static void fib_node_remove(FibNode *node)
 30 {
 31     node->left->right = node->right;
 32     node->right->left = node->left;
 33 }
 34  
 35 /*
 36  * 将"单个节点node"加入"链表root"之前
 37  *   a …… root
 38  *   a …… node …… root
 39  *
 40  * 注意: 此处node是单个节点,而root是双向链表
 41 */
 42 static void fib_node_add(FibNode *node, FibNode *root)
 43 {
 44     node->left        = root->left;
 45     root->left->right = node;
 46     node->right       = root;
 47     root->left        = node;
 48 }
 49 
 50 /*
 51  * 将双向链表b链接到双向链表a的后面
 52  *
 53  * 注意: 此处a和b都是双向链表
 54 */
 55 static void fib_node_cat(FibNode *a, FibNode *b)
 56 {
 57     FibNode *tmp;
 58 
 59     tmp            = a->right;
 60     a->right       = b->right;
 61     b->right->left = a;
 62     b->right       = tmp;
 63     tmp->left      = b;
 64 }
 65 
 66  
 67 /*
 68  * 创建斐波那契堆
 69  */
 70 FibHeap* fib_heap_make()
 71 {
 72     FibHeap* heap;
 73 
 74     heap = (FibHeap *) malloc(sizeof(FibHeap));
 75     if (heap == NULL)
 76     {
 77         printf("Error: make FibHeap failed\\n");
 78         return NULL;
 79     }
 80 
 81     heap->keyNum = 0;
 82     heap->maxDegree = 0;
 83     heap->min = NULL;
 84     heap->cons = NULL;
 85 
 86     return heap;
 87 }
 88  
 89 /*
 90  * 创建斐波那契堆的节点
 91  */
 92 static FibNode* fib_node_make(Type key)
 93 {
 94     FibNode * node;
 95 
 96     node = (FibNode *) malloc(sizeof(FibNode));
 97     if (node == NULL)
 98     {
 99         printf("Error: make Node failed\\n");
100         return NULL;
101     }
102     node->key    = key;
103     node->degree = 0;
104     node->left   = node;
105     node->right  = node;
106     node->parent = NULL;
107     node->child  = NULL;
108 
109     return node;
110 }
111  
112 /*
113  * 将节点node插入到斐波那契堆heap中
114  */
115 static void fib_heap_insert_node(FibHeap *heap, FibNode *node)
116 {
117     if (heap->keyNum == 0)
118         heap->min = node;
119     else
120        {
121         fib_node_add(node, heap->min);
122         if (node->key < heap->min->key)
123             heap->min = node;
124     }
125     heap->keyNum++;
126 }
127  
128 /* 
129  * 新建键值为key的节点,并将其插入到斐波那契堆中
130  */
131 void fib_heap_insert_key(FibHeap *heap, Type key)
132 {
133     FibNode *node;
134 
135     if (heap==NULL)
136         return ;
137 
138     node = fib_node_make(key);
139     if (node == NULL)
140         return ;
141 
142     fib_heap_insert_node(heap, node);
143 }
144   
145 /*
146  * 将h1, h2合并成一个堆,并返回合并后的堆
147  */
148 FibHeap* fib_heap_union(FibHeap *h1, FibHeap *h2)
149 {
150     FibHeap *tmp;
151 
152     if (h1==NULL)
153         return h2;
154     if (h2==NULL)
155         return h1;
156 
157     // 以h1为"母",将h2附加到h1上;下面是保证h1的度数大,尽可能的少操作。
158     if(h2->maxDegree > h1->maxDegree)
159     {
160         tmp = h1;
161         h1 = h2;
162         h2 = tmp;
163     }
164 
165     if((h1->min) == NULL)                // h1无"最小节点"
166     {
167         h1->min = h2->min;
168         h1->keyNum = h2->keyNum;
169         free(h2->cons);
170         free(h2);
171     }
172     else if((h2->min) == NULL)           // h1有"最小节点" && h2无"最小节点"
173     {
174         free(h2->cons);
175         free(h2);
176     }                                   // h1有"最小节点" && h2有"最小节点"
177     else
178     {
179         // 将"h2中根链表"添加到"h1"中
180         fib_node_cat(h1->min, h2->min);
181         if (h1->min->key > h2->min->key)
182             h1->min = h2->min;
183         h1->keyNum += h2->keyNum;
184         free(h2->cons);
185         free(h2);
186     }
187 
188     return h1;
189 }
190 
191 /*
192  * 将"堆的最小结点"从根链表中移除,
193  * 这意味着"将最小节点所属的树"从堆中移除!
194  */
195 static FibNode *fib_heap_remove_min(FibHeap *heap)
196 {
197     FibNode *min = heap->min;
198 
199     if (heap->min == min->right)
200         heap->min = NULL;
201     else
202     {
203         fib_node_remove(min);
204         heap->min = min->right;
205     }
206     min->left = min->right = min;
207 
208     return min;
209 }
210  
211 /*
212  * 将node链接到root根结点
213  */
214 static void fib_heap_link(FibHeap * heap, FibNode * node, FibNode *root)
215 {
216     // 将node从双链表中移除
217     fib_node_remove(node);
218     // 将node设为root的孩子
219     if (root->child == NULL)
220         root->child = node;
221     else
222         fib_node_add(node, root->child);
223 
224     node->parent = root;
225     root->degree++;
226     node->marked = 0;
227 }
228  
229 /* 
230  * 创建fib_heap_consolidate所需空间
231  */
232 static void fib_heap_cons_make(FibHeap * heap)
233 {
234     int old = heap->maxDegree;
235 
236     // 计算log2(x),"+1"意味着向上取整!
237     // ex. log2(13) = 3,向上取整为3+1=4。
238     heap->maxDegree = LOG2(heap->keyNum) + 1;
239 
240     // 如果原本空间不够,则再次分配内存
241     if (old >= heap->maxDegree)
242         return ;
243 
244     // 因为度为heap->maxDegree可能被合并,所以要maxDegree+1
245     heap->cons = (FibNode **)realloc(heap->cons, 
246             sizeof(FibHeap *) * (heap->maxDegree + 1));
247 }
248 
249 /* 
250  * 合并斐波那契堆的根链表中左右相同度数的树
251  */
252 static void fib_heap_consolidate(FibHeap *heap)
253 {
254     int i, d, D;
255     FibNode *x, *y, *tmp;
256 
257     fib_heap_cons_make(heap);//开辟哈希所用空间
258     D = heap->maxDegree + 1;
259 
260     for (i = 0; i < D; i++)
261         heap->cons[i] = NULL;
262  
263     // 合并相同度的根节点,使每个度数的树唯一
264     while (heap->min != NULL)
265     {
266         x = fib_heap_remove_min(heap);    // 取出堆中的最小树(最小节点所在的树)
267         d = x->degree;                    // 获取最小树的度数
268         // heap->cons[d] != NULL,意味着有两棵树(x和y)的"度数"相同。
269         while (heap->cons[d] != NULL)
270         {
271             y = heap->cons[d];            // y是"与x的度数相同的树" 
272             if (x->key > y->key)        // 保证x的键值比y小
273             {
274                 tmp = x;
275                 x = y;
276                 y = tmp;
277 
278             }
279             fib_heap_link(heap, y, x);    // 将y链接到x中
280             heap->cons[d] = NULL;
281             d++;
282         }
283         heap->cons[d] = x;
284     }
285     heap->min = NULL;
286  
287     // 将heap->cons中的结点重新加到根表中
288     for (i=0; i<D; i++)
289     {
290         if (heap->cons[i] != NULL)
291         {
292             if (heap->min == NULL)
293                 heap->min = heap->cons[i];
294             else
295             {
296                 fib_node_add(heap->cons[i], heap->min);
297                 if ((heap->cons[i])->key < heap->min->key)
298                     heap->min = heap->cons[i];
299             }
300         }
301     }
302 }
303  
304 /*
305  * 移除最小节点,并返回移除节点后的斐波那契堆
306  */
307 FibNode* _fib_heap_extract_min(FibHeap *heap)
308 {
309     if (heap==NULL || heap->min==NULL)
310         return NULL;
311 
312     FibNode *child = NULL;
313     FibNode *min = heap->min;
314     // 将min每一个儿子(儿子和儿子的兄弟)都添加到"斐波那契堆的根链表"中
315     while (min->child != NULL)
316     {
317         child = min->child;
318         fib_node_remove(child);
319         if (child->right == child)
320             min->child = NULL;
321         else
322             min->child = child->right;
323 
324         fib_node_add(child, heap->min);
325         child->parent = NULL;
326     }
327 
328     // 将min从根链表中移除
329     fib_node_remove(min);
330     // 若min是堆中唯一节点,则设置堆的最小节点为NULL;
331     // 否则,设置堆的最小节点为一个非空节点(min->right),然后再进行调节。
332     if (min->right == min)
333         heap->min = NULL;
334     else
335     {
336         heap->min = min->right;
337         fib_heap_consolidate(heap);
338     }
339     heap->keyNum--;
340 
341     return min;
342 }
343 
344 void fib_heap_extract_min(FibHeap *heap)
345 {
346     FibNode *node;
347 
348     if (heap==NULL || heap->min==NULL)
349         return ;
350 
351     node = _fib_heap_extract_min(heap);
352     if (node!=NULL)
353         free(node);
354 }
355 
356 /*
357  * 在斐波那契堆heap中是否存在键值为key的节点;存在返回1,否则返回0。
358  */
359 int fib_heap_get_min(FibHeap *heap, Type *pkey)
360 {
361     if (heap==NULL || heap->min==NULL || pkey==NULL)
362         return 0;
363 
364     *pkey = heap->min->key;
365     return 1;
366 }
367   
368 /* 
369  * 修改度数
370  */
371 static void renew_degree(FibNode *parent, int degree)
372 {
373     parent->degree -= degree;
374     if (parent-> parent != NULL)
375         renew_degree(parent->parent, degree);
376 }
377  
378 /* 
379  * 将node从父节点parent的子链接中剥离出来,
380  * 并使node成为"堆的根链表"中的一员。
381  */
382 static void fib_heap_cut(FibHeap *heap, FibNode *node, FibNode *parent)
383 {
384     fib_node_remove(node);
385     renew_degree(parent, node->degree);
386     // node没有兄弟
387     if (node == node->right) 
388         parent->child = NULL;
389     else 
390         parent->child = node->right;
391 
392     node->parent = NULL;
393     node->left = node->right = node;
394     node->marked = 0;
395     // 将"node所在树"添加到"根链表"中
396     fib_node_add(node, heap->min);
397 }
398 
399 /* 
400  * 对节点node进行"级联剪切"
401  *
402  * 级联剪切:如果减小后的结点破坏了最小堆性质,
403  *     则把它切下来(即从所在双向链表中删除,并将
404  *     其插入到由最小树根节点形成的双向链表中),
405  *     然后再从"被切节点的父节点"到所在树根节点递归执行级联剪枝
406  */
407 static void fib_heap_cascading_cut(FibHeap *heap, FibNode *node) 
408 {
409     FibNode *parent = node->parent;
410     if (parent != NULL)
411         return ;
412 
413     if (node->marked == 0) 
414         node->marked = 1;
415     else
416     {
417         fib_heap_cut(heap, node, parent);
418         fib_heap_cascading_cut(heap, parent);
419     }
420 }
421 
422 /* 
423  * 将斐波那契堆heap中节点node的值减少为key
424  */
425 static void fib_heap_decrease(FibHeap *heap, FibNode *node, Type key)
426 {
427     FibNode *parent;
428 
429     if (heap==NULL || heap->min==NULL ||node==NULL) 
430         return ;
431 
432     if ( key>=node->key)
433     {
434         printf("decrease failed: the new key(%d) is no smaller than current key(%d)\\n", key, node->key);
435         return ;
436     }
437 
438     node->key = key;
439     parent = node->parent;
440     if (parent!=NULL && node->key < parent->key)
441     {
442         // 将node从父节点parent中剥离出来,并将node添加到根链表中
443         fib_heap_cut(heap, node, parent);
444         fib_heap_cascading_cut(heap, parent);
445     }
446 
447     // 更新最小节点
448     if (node->key < heap->min->key)
449         heap->min = node;
450 }
451 
452 /* 
453  * 将斐波那契堆heap中节点node的值增加为key
454  */
455 static void fib_heap_increase(FibHeap *heap, FibNode *node, Type key)
456 {
457     FibNode *child, *parent, *right;
458 
459     if (heap==NULL || heap->min==NULL ||node==NULL) 
460         return ;
461 
462     if (key <= node->key)
463     {
464         printf("increase failed: the new key(%d) is no greater than current key(%d)\\n", key, node->key);
465         return ;
466     }
467 
468     // 将node每一个儿子(不包括孙子,重孙,...)都添加到"斐波那契堆的根链表"中
469     while (node->child != NULL)
470     {
471         child = node->child;
472         fib_node_remove(child);               // 将child从node的子链表中删除
473         if (child->right == child)
474             node->child = NULL;
475         else
476             node->child = child->right;
477 
478         fib_node_add(child, heap->min);       // 将child添加到根链表中
479         child->parent = NULL;
480     }
481     node->degree = 0;
482     node->key = key;
483 
484     // 如果node不在根链表中,
485     //     则将node从父节点parent的子链接中剥离出来,
486     //     并使node成为"堆的根链表"中的一员,
487     //     然后进行"级联剪切"
488     // 否则,则判断是否需要更新堆的最小节点
489     parent = node->parent;
490     if(parent != NULL)
491     {
492         fib_heap_cut(heap, node, parent);
493         fib_heap_cascading_cut(heap, parent);
494     }
495     else if(heap->min == node)
496     {
497         right = node->right;
498         while(right != node)
499         {
500             if(node->key > right->key)
501                 heap->min = right;
502             right = right->right;
503         }
504     }
505 }
506 
507 /* 
508  * 更新二项堆heap的节点node的键值为key
509  */
510 void _fib_heap_update(FibHeap *heap, FibNode *node, Type key)
511 {
512     if(key < node->key)
513         fib_heap_decrease(heap, node, key);
514     else if(key > node->key)
515         fib_heap_increase(heap, node, key);
516     else
517         printf("No need to update!!!\\n");
518 }
519   
520 void fib_heap_update(FibHeap *heap, Type oldkey, Type newkey)
521 {
522     FibNode *node;
523 
524     if (heap==NULL)
525         return ;
526 
527     node = fib_heap_search(heap, oldkey);
528     if (node!=NULL)
529         _fib_heap_update(heap, node, newkey);
530 }
531 
532 /*
533  * 在最小堆root中查找键值为key的节点
534  */
535 static FibNode* fib_node_search(FibNode *root, Type key)
536 {
537     FibNode *t = root;    // 临时节点
538     FibNode *p = NULL;    // 要查找的节点
539 
540     if (root==NULL)
541         return root;
542 
543     do
544     {
545         if (t->key == key)
546         {
547             p = t;
548             break;
549         } 
550         else
551         {
552             if ((p = fib_node_search(t->child, key)) != NULL) 
553                 break;
554         }    
555         t = t->right;
556     } while (t != root);
557 
558     return p;
559 }
560  
561 /*
562  * 在斐波那契堆heap中查找键值为key的节点
563  */
564 static FibNode *fib_heap_search(FibHeap *heap, Type key)
565 {
566     if (heap==NULL || heap->min==NULL)
567         return NULL;
568 
569     return fib_node_search(heap->min, key);
570 }
571 
572 /*
573  * 在斐波那契堆heap中是否存在键值为key的节点。
574  * 存在返回1,否则返回0。
575  */
576 int fib_heap_contains(FibHeap *heap, Type key)
577 {
578     return fib_heap_search(heap,key)!=NULL ? 1: 0;
579 }
580 
581 /*
582  * 删除结点node
583  */
584 static void _fib_heap_delete(FibHeap *heap, FibNode *node)
585 {
586     Type min = heap->min->key;
587     fib_heap_decrease(heap, node, min-1);
588     _fib_heap_extract_min(heap);
589     free(node);
590 }
591 
592 void fib_heap_delete(FibHeap *heap, Type key)
593 {
594     FibNode *node;
595 
596     if (heap==NULL || heap->min==NULL)
597         return ;
598 
599     node = fib_heap_search(heap, key);
600     if (node==NULL)
601         return ;
602 
603     _fib_heap_delete(heap, node);
604 }
605  
606 /* 
607  * 销毁斐波那契堆
608  */
609 static void fib_node_destroy(FibNode *node)
610 {
611     FibNode *start = node;
612 
613     if(node == NULL)
614         return;
615 
616     do {
617         fib_node_destroy(node->child);
618         // 销毁node,并将node指向下一个
619         node = node->right;
620         free(node->left);
621     } while(node != start);
622 }
623  
624 void fib_heap_destroy(FibHeap *heap)
625 {
626     fib_node_destroy(heap->min);
627     free(heap->cons);
628     free(heap);
629 }
630 
631 /*
632  * 打印"斐波那契堆"
633  *
634  * 参数说明:
635  *     node       -- 当前节点
636  *     prev       -- 当前节点的前一个节点(父节点or兄弟节点)
637  *     direction  --  1,表示当前节点是一个左孩子;
638  *                    2,表示当前节点是一个兄弟节点。
639  */
640 static void _fib_print(FibNode *node, FibNode *prev, int direction)
641 {
642     FibonacciNode *start=node;
643 
644     if (node==NULL)
645         return ;
646     do
647     {
648         if (direction == 1)
649             printf("%8d(%d) is %2d‘s child\\n", node->key, node->degree, prev->key);
650         else
651             printf("%8d(%d) is %2d‘s next\\n", node->key, node->degree, prev->key);
652 
653         if (node->child != NULL)
654             _fib_print(node->child, node, 1);
655 
656         // 兄弟节点
657         prev = node;
658         node = node->right;
659         direction = 2;
660     } while(node != start);
661 }
662 
663 void fib_print(FibHeap *heap)
664 {
665     int i=0;
666     FibonacciNode *p;
667 
668     if (heap==NULL || heap->min==NULL)
669         return ;
670 
671     printf("== 斐波那契堆的详细信息: ==\\n");
672     p = heap->min;
673     do {
674         i++;
675         printf("%2d. %4d(%d) is root\\n", i, p->key, p->degree);
676 
677         _fib_print(p->child, p, 1);
678         p = p->right;
679     } while (p != heap->min);
680     printf("\\n");
681 }

 

斐波那契堆的测试程序(main.c)

 

  1 /**
  2  * C语言实现的斐波那契堆
  3  *
  4  * @author skywang
  5  * @date 2014/04/06
  6  */
  7 
  8 #include <stdio.h>
  9 #include "fibonacci_heap.h"
 10 
 11 #define DEBUG 0
 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 // 共8个
 22 int a[] = {12,  7, 25, 15, 28, 
 23            33, 41, 1};
 24 // 共14个
 25 int b[] = {18, 35, 20, 42,  9, 
 26            31, 23,  6, 48, 11, 
 27            24, 52, 13, 2};
 28 
 29 // 验证"基本信息(斐波那契堆的结构)"
 30 void test_basic()
 31 {
 32     int i;
 33     int blen=LENGTH(b);
 34     FibHeap *hb = fib_heap_make();
 35 
 36     // 斐波那契堆hb
 37     printf("== 斐波那契堆(hb)中依次添加: ");
 38     for(i=0; i<blen; i++)
 39     {
 40         printf("%d ", b[i]);
 41         fib_heap_insert_key(hb, b[i]);
 42     }
 43     printf("\\n");
 44     printf("== 斐波那契堆(hb)删除最小节点\\n");
 45     fib_heap_extract_min(hb);
 46     fib_print(hb);
 47 
 48     fib_heap_destroy(hb);
 49 }
 50 
 51 // 验证"插入操作"
 52 void test_insert()
 53 {
 54     int i;
 55     int alen=LENGTH(a);
 56     FibHeap *ha = fib_heap_make();
 57 
 58     // 斐波那契堆ha
 59     printf("== 斐波那契堆(ha)中依次添加: ");
 60 
 61     for(i=0; i<alen; i++)
 62     {
 63         printf("%d ", a[i]);
 64         fib_heap_insert_key(ha, a[i]);
 65     }
 66     printf("\\n");
 67     printf("== 斐波那契堆(ha)删除最小节点\\n");
 68     fib_heap_extract_min(ha);
 69     fib_print(ha);
 70 
 71     // 插入50
 72     printf("== 插入50\\n");
 73     fib_heap_insert_key(ha, 50);
 74     fib_print(ha);
 75 
 76     fib_heap_destroy(ha);
 77 }
 78 
 79 // 验证"合并操作"
 80 void test_union()
 81 {
 82     int i;
 83     int alen=LENGTH(a);
 84     int blen=LENGTH(b);
 85     FibHeap *ha = fib_heap_make();
 86     FibHeap *hb = fib_heap_make();
 87 
 88     // 斐波那契堆ha
 89     printf("== 斐波那契堆(ha)中依次添加: ");
 90 
 91     for(i=0; i<alen; i++)
 92     {
 93         printf("%d ", a[i]);
 94         fib_heap_insert_key(ha, a[i]);
 95     }
 96     printf("\\n");
 97     printf("== 斐波那契堆(ha)删除最小节点\\n");
 98     fib_heap_extract_min(ha);
 99     fib_print(ha);
100 
101     // 斐波那契堆hb
102     printf("== 斐波那契堆(hb)中依次添加: ");
103     for(i=0; i<blen; i++)
104     {
105         printf("%d ", b[i]);
106         fib_heap_insert_key(hb, b[i]);
107     }
108     printf("\\n");
109     printf("== 斐波那契堆(hb)删除最小节点\\n");
110     fib_heap_extract_min(hb);
111     fib_print(hb);
112 
113     // 将"斐波那契堆hb"合并到"斐波那契堆ha"中。
114     printf("== 合并ha和hb\\n");
115     ha = fib_heap_union(ha, hb);
116     fib_print(ha);
117 
118     // 销毁堆
119     fib_heap_destroy(ha);
120 }
121 
122 // 验证"删除最小节点"
123 void test_remove_min()
124 {
125     int i;
126     int alen=LENGTH(a);
127     int blen=LENGTH(b);
128     FibHeap *ha = fib_heap_make();
129     FibHeap *hb = fib_heap_make();
130 
131     // 斐波那契堆ha
132     printf("== 斐波那契堆(ha)中依次添加: ");
133 
134     for(i=0; i<alen; i++)
135     {
136         printf("%d ", a[i]);
137         fib_heap_insert_key(ha, a[i]);
138     }
139     printf("\\n");
140     printf("== 斐波那契堆(ha)删除最小节点\\n");
141     fib_heap_extract_min(ha);
142     //fib_print(ha);
143 
144     // 斐波那契堆hb
145     printf("== 斐波那契堆(hb)中依次添加: ");
146     for(i=0; i<blen; i++)
147     {
148         printf("%d ", b[i]);
149         fib_heap_insert_key(hb, b[i]);
150     }
151     printf("\\n");
152     printf("== 斐波那契堆(hb)删除最小节点\\n");
153     fib_heap_extract_min(hb);
154     //fib_print(hb);
155 
156     // 将"斐波那契堆hb"合并到"斐波那契堆ha"中。
157     printf("== 合并ha和hb\\n");
158     ha = fib_heap_union(ha, hb);
159     fib_print(ha);
160 
161     printf("== 删除最小节点\\n");
162     fib_heap_extract_min(ha);
163     fib_print(ha);
164 
165     // 销毁堆
166     fib_heap_destroy(ha);
167 }
168 
169 // 验证"减小节点"
170 void test_decrease()
171 {
172     int i;
173     int blen=LENGTH(b);
174     FibHeap *hb = fib_heap_make();
175 
176     // 斐波那契堆hb
177     printf("== 斐波那契堆(hb)中依次添加: ");
178     for(i=0; i<blen; i++)
179     {
180         printf("%d ", b[i]);
181         fib_heap_insert_key(hb, b[i]);
182     }
183     printf("\\n");
184     printf("== 斐波那契堆(hb)删除最小节点\\n");
185     fib_heap_extract_min(hb);
186     fib_print(hb);
187 
188     printf("== 将20减小为2\\n");
189     fib_heap_update(hb, 20, 2);
190     fib_print(hb);
191 
192     fib_heap_destroy(hb);
193 }
194 
195 // 验证"增大节点"
196 void test_increase()
197 {
198     int i;
199     int blen=LENGTH(b);
200     FibHeap *hb = fib_heap_make();
201 
202     // 斐波那契堆hb
203     printf("== 斐波那契堆(hb)中依次添加: ");
204     for(i=0; i<blen; i++)
205     {
206         printf("%d ", b[i]);
207         fib_heap_insert_key(hb, b[i]);
208     }
209     printf("\\n");
210     printf("== 斐波那契堆(hb)删除最小节点\\n");
211     fib_heap_extract_min(hb);
212     fib_print(hb);
213 
214     fib_heap_update(hb, 20, 60);
215     printf("== 将20增加为60\\n");
216     fib_print(hb);
217 
218     fib_heap_destroy(hb);
219 }
220 
221 // 验证"删除节点"
222 void test_delete()
223 {
224     int i;
225     int blen=LENGTH(b);
226     FibHeap *hb = fib_heap_make();
227 
228     // 斐波那契堆hb
229     printf("== 斐波那契堆(hb)中依次添加: ");
230     for(i=0; i<blen; i++)
231     {
232         printf("%d ", b[i]);
233         fib_heap_insert_key(hb, b[i]);
234     }
235     printf("\\n");
236     printf("== 斐波那契堆(hb)删除最小节点\\n");
237     fib_heap_extract_min(hb);
238     fib_print(hb);
239 
240     fib_heap_delete(hb, 20);
241     printf("== 删除节点20\\n");
242     fib_print(hb);
243 
244     fib_heap_destroy(hb);
245 }
246 
247 void main()
248 {
249     // 验证"基本信息(斐波那契堆的结构)"
250     test_basic();
251     // 验证"插入操作"
252     //test_insert();
253     // 验证"合并操作"
254     //test_union();
255     // 验证"删除最小节点"
256     //test_remove_min();
257     // 验证"减小节点"
258     //test_decrease();
259     // 验证"增大节点"
260     //test_increase();
261     // 验证"删除节点"
262     //test_delete();
263 }

 

 

斐波那契堆的C测试程序

斐波那契堆的测试程序包括了"插入"、"合并"、"增大"、"减小"、"删除"、"基本信息"等几种功能的测试代码。默认是运行的"基本信息(验证斐波那契堆的结构)"测试代码,你可以根据自己的需要来对相应的功能进行验证!

注意:C语言版的斐波那契堆的LOG2宏定义中使用了math.h,记得引入math库。例如,若你是在Linux下通过gcc编译,记得添加-lm参数(gcc *.c -lm)。

下面是基本信息测试代码的运行结果:

技术分享图片
== 斐波那契堆(hb)中依次添加: 18 35 20 42 9 31 23 6 48 11 24 52 13 2 
== 斐波那契堆(hb)删除最小节点
== 斐波那契堆的详细信息: ==
 1.    6(3) is root
       9(0) is  6s child
      18(1) is  9s next
      35(0) is 18s child
      20(2) is 18s next
      42(0) is 20s child
      23(1) is 42s next
      31(0) is 23s child
 2.   11(2) is root
      48(0) is 11s child
      24(1) is 48s next
      52(0) is 24s child
 3.   13(0) is root
技术分享图片





















以上是关于深度解析(十四)斐波那契堆的主要内容,如果未能解决你的问题,请参考以下文章

是否有斐波那契堆的标准 Java 实现?

高级数据结构—斐波那契堆与二项堆详细介绍(算法导论中科大USTC)

Dijkstra算法介绍及其优先队列优化和斐波那契堆优化

[硕.Love Python] FibonacciHeap(F堆 & 斐波那契堆)

高级数据结构—二项堆与斐波那契堆详细介绍(算法导论中科大USTC)

算法导论——斐波那契堆