## 斐波那契堆的介绍 ##
斐波那契堆是堆的一种,它和二项堆一样,也是一种可合并堆,可用于实现合并优先队列。而斐波那契堆比二项堆具有更好的平摊分析性能,它的合并操作的时间复杂度是O(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;
}FibonacciNode FibNode;
FibNode是斐波那契堆的节点类,它包含的信息比较多。key适用于比较节点大小的,degree 是记录节点的度的,left和right 分别指向节点的左右兄弟,child是节点的第一个孩子,parnet是节点的父亲,marked是记录该节点是否被删除第一个孩子(marked在删除节点是有用).
typedef struct _FibonacciHeap
{
int keyNum; //堆中节点的总数
int maxDegree; //最大度
struct _FibonacciNode *min; //最小节点(某个最小堆的根节点)
struct _FibonacciNode **cons;//最大度的内存区域
}FibonacciHeap,FibHeap;
FibHeap是斐波那契对应的类。min是保存当前堆的最小节点,keyNum用于记录节点的总数,,maxDegree用于记录堆的最大度,而cons在删除节点时来暂时保存堆 数据的临时空间,
## 斐波那契堆的内存结构图 ##
从图中可以看出,斐波那契堆是由一组最小堆构成,这些最小堆的根节点组成了双向链表(后文称之为根链表)斐波那契堆中的最小节点就是根链表中的最小节点。。
## 2.插入操作 ##
插入操作非常简单,插入一个节点到堆中,直接将该节点插入到“根链表的min节点”之前即可;若被插入节点比min节点小,则更新min节点为被插入节点。
如图所示:
斐波那契堆的根链表是双向链表,这里将min节点看成是双向链表的表头。在插入节点时,每次都是将节点插入到min节点之前(即插入到双链表末尾)。此外,对于根链表中最小堆只有一个节点的情况,插入操作就会演化成双向链表的插入操作。
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_heap_insert_node(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;
}
## 取出最小节点 ##
抽取最小节点操作是斐波那契堆中较复杂的操作。
(1)将要抽取最小节点的子树都直接串联在根表中;
(2)合并所有DEGFREE相等的树,直到没有相等的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;
}
## 减小节点值 ##
减少斐波那契堆中的节点的键值,这个操作的难点是:如果减少节点后破坏了”最小堆”性质,如何去维护呢?下面对一般性情况进行分析。
(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;
}
## 增加节点值 ##
增加节点值和减少节点值类似,这个操作的难点也是如何维护”最小堆”性质。思路如下:
(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;
}
}
}
## 删除节点 ##
删除节点,本文采用了操作是:”取出最小节点”和”减小节点值”的组合。
(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);
}