也许我们每个人都知道斐波那契数列(Fibonacci sequence)。即这样一个数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144...,如果我们用伪代码比表示:
int FibonacciSequence(int n){
if (n == 1 || n == 2) {
return 1;
}
return FibonacciSequence(n - 1) + FibonacciSequence(n - 2);
}
接下来我们会知道为什么叫斐波那契堆,本文不涉及任何的分析(这里平摊分析(Amortized analysis))。
1. 概述
一个斐波那契堆是一系列具有最小堆序的有根树的集合。(也就是每棵树都遵循最小堆性质)[1]
2. 斐波那契堆的结构
[2]
从上面我们可能发现,我们堆斐波那契堆的描述,并不完全符合我们的定义(那时因为定义是针对在一次extractMin(删除最小值)操作后,使之符合我们的定义)。但从上面的结构图我们很容易看出其详细的结构,以及指针域。兄弟节点之间用双向链表进行链接,父节点指向其中一个孩子节点,而所有的子节点都会指向其父亲节点。有些节点还被标记了(后面我们会知道,只有当父节点失去孩子节点时,会被标记)。上述还有一个隐含的关键字那就是每个节点的度(即拥有孩子节点的个数)
3. 基本数据结构
typedef int ElemType;
// the structure of each node of this heap
typedef struct FiboNode{
// the key_value
ElemType data;
struct FiboNode *p;
struct FiboNode *child;
struct FiboNode *left;
struct FiboNode *right;
// the number of its children
int degree;
// indicates whether the node lost a child
int marked;
} FiboNode;
// generate a node
FiboNode * generateFiboNode(ElemType data) {
FiboNode * fn = NULL;
fn = (FiboNode *) malloc(sizeof(FiboNode));
if (fn != NULL) {
fn->p = NULL;
fn->child = NULL;
fn->left = NULL;
fn->right = NULL;
fn->data = data;
fn->marked = FALSE;
} else {
printf("memory allocation of FiboNode failed");
}
return fn;
}
// represent the fibonacci heap
typedef struct FibonacciHeap{
// pointer pointing to min key in the heap
FiboNode *min;
// the number of nodes the heap containing
int n;
} *FibonacciHeap;
// create Fibonacci Heap, and return it
FibonacciHeap makeFiboHeap() {
FibonacciHeap h = NULL;
h = (struct FibonacciHeap *) malloc(sizeof(struct FibonacciHeap));
if (h == NULL) {
printf("memory allocation of FibonacciHeap failed");
}
h->min = NULL;
h->n = 0;
return h;
}
4. 插入
插入其实很简单,就是把待插入的元素插入根列表(root list)中去,即上图[2]中min[H]指向的那一行。
// union two heap
FibonacciHeap heapUnion(FibonacciHeap *h1, FibonacciHeap *h2) {
// using a new heap
FibonacciHeap h = makeFiboHeap();
if (h != NULL) {
// concatenate the root list of h with h1 and h2
if ((*h1)->min == NULL) {
h->min = (*h2)->min;
} else if ((*h2)->min == NULL) {
h->min = (*h1)->min;
} else {
FiboNode *min_h1 = (*h1)->min;
FiboNode *min_right_h1 = min_h1->right;
FiboNode *min_h2 = (*h2)->min;
FiboNode *min_right_h2 = min_h2->right;
min_h1->right = min_right_h2;
min_right_h2->left = min_h1;
min_h2->right = min_right_h1;
min_right_h1->left = min_h2;
if ((*h1)->min->data > (*h2)->min->data) {
h->min = (*h2)->min;
} else {
h->min = (*h1)->min;
}
}
// release the free memory
free(*h1);
*h1 = NULL;
free(*h2);
*h2 = NULL;
// update the n
h->n = (*h1)->n + (*h2)->n;
}
return h;
}
5. 合并(Union)
这个操作只需要将两个斐波那契堆的根列表合并,并使得min[H]指向两者间的最小值节点。
// union two heap
FibonacciHeap heapUnion(FibonacciHeap *h1, FibonacciHeap *h2) {
// using a new heap
FibonacciHeap h = makeFiboHeap();
if (h != NULL) {
// concatenate the root list of h with h1 and h2
if ((*h1)->min == NULL) {
h->min = (*h2)->min;
} else if ((*h2)->min == NULL) {
h->min = (*h1)->min;
} else {
FiboNode *min_h1 = (*h1)->min;
FiboNode *min_right_h1 = min_h1->right;
FiboNode *min_h2 = (*h2)->min;
FiboNode *min_right_h2 = min_h2->right;
min_h1->right = min_right_h2;
min_right_h2->left = min_h1;
min_h2->right = min_right_h1;
min_right_h1->left = min_h2;
if ((*h1)->min->data > (*h2)->min->data) {
h->min = (*h2)->min;
} else {
h->min = (*h1)->min;
}
}
// release the free memory
free(*h1);
*h1 = NULL;
free(*h2);
*h2 = NULL;
// update the n
h->n = (*h1)->n + (*h2)->n;
}
return h;
}
6. 抽取最小节点
从上面我们很容易知道如何操作的。
1)具体步骤如下:
step1. 让最小节点的的所有子节点添加到根列表中去。
step2. 从根列表中删除最小节点。
step3. 创建一个log(n) + 1 的节点指针数组A。
step4. 对根列表进行合并,使得其满足我们堆斐波那契堆的定义(即根列表中的节点的度数具有唯一性)
// extract minimum node in this heap
FiboNode * extractMinimum(FibonacciHeap h) {
// the minimum node
FiboNode * z = h->min;
if (z != NULL) {
FiboNode * firstChid = z->child;
// add the children of minimum node to the root list.
if (firstChid != NULL) {
FiboNode * sibling = firstChid->right;
// min_right point the right node of minimum node
FiboNode * min_right = z->right;
// add the first child to the root list
z->right = firstChid;
firstChid->left = z;
min_right->left = firstChid;
firstChid->right = min_right;
firstChid->p = NULL;
min_right = firstChid;
while (firstChid != sibling) {
// record the right sibling of sibling
FiboNode *sibling_right = sibling->right;
z->right = sibling;
sibling->left = z;
sibling->right = min_right;
min_right->left = sibling;
min_right = sibling;
sibling = sibling_right;
// update the p
sibling->p = NULL;
}
}
// remove z from the root list
z->left->right = z->right;
z->right->left = z->left;
// the root list has only one node
if (z == z->right) {
h->min = NULL;
// the children of z shoud be the root list of the heap
// and find the minimum in this heap
if (z->child != NULL) {
FiboNode *child = z->child;
h->min = child;
FiboNode *sibling = child->right;
while (child != sibling) {
if (h->min->data > sibling->data) {
h->min = sibling;
}
sibling = sibling->right;
}
}
} else {
h->min = z->right;
consolidate(h);
}
h->n -= 1;
}
return z;
}
2)对根列表进行合并
1. link操作
把节点y链接到x:并把y从根列表中删除,此时x会成为y的父亲,y会成为x的孩子,同时x节点的度数(degree)会增加1.
// make y a child of x
void heapLink(FibonacciHeap h, FiboNode *y, FiboNode *x) {
// remove y from the root list of h
y->left->right = y->right;
y->right->left = y->left;
// make y a child of x, incrementing x.degree
FiboNode * child = x->child;
if (child == NULL) {
x->child = y;
y->left = y;
y->right = y;
} else {
y->right = child->right;
child->right->left = y;
y->left = child;
child->right = y;
}
y->p = x;
x->degree += 1;
y->marked = FALSE;
}
2. 合并
创建一个辅助数组A[0, 1..., D(H.n)]来记录根节点对应的度数的轨迹。如果A[i] = y, 则degree[y] = i.通过遍历根列表,若对应度数的数组A[i]为NILL,表示还没有记录,则使得A[i] = y, 否则存在两个度数相同的度数的根列表节点,则我们需要对其进行link操作,使之度数增加1,并设置A[i] = NILL, 然后再探查A[i + 1]是否为NIll, 为NILL则记录,否则再进行link操作,这样循环往复。
void consolidate(FibonacciHeap h) {
int dn = (int)(log(h->n) / log(2)) + 1;
FiboNode * A[dn];
int i;
for (i = 0; i < dn; ++i) {
A[i] = NULL;
}
// the first node we will consolidate
FiboNode *w = h->min;
// the final node in this heap we will consolidate
FiboNode *f = w->left;
FiboNode *x = NULL;
FiboNode *y = NULL;
int d;
// temp
FiboNode *t = NULL;
while (w != f) {
d = w->degree;
x = w;
w = w->right;
while (A[d] != NULL) {
// another node with the same degree as x
y = A[d];
if(x->data > y->data) {
t = x;
x = y;
y = t;
}
heapLink(h, y, x);
A[d] = NULL;
d += 1;
}
A[d] = x;
}
// the last node to consolidate (f == w)
d = w->degree;
x = w;
while (A[d] != NULL) {
// another node with the same degree as x
y = A[d];
if(x->data > y->data) {
t = x;
x = y;
y = t;
}
heapLink(h, y, x);
A[d] = NULL;
d += 1;
}
A[d] = x;
int min_key = 100000;
h->min = NULL;
// to get min in this heap
for (i = 0; i < dn; ++i) {
if(A[i] != NULL && A[i]->data < min_key) {
h->min = A[i];
min_key = A[i]->data;
}
}
}
7. 降低节点的值
直接上代码这样,接下来分析才比较容易。// decrease the data of given node to the key
void heapDecreaseKey(FibonacciHeap h, FiboNode *x, ElemType key) {
if (key >= x->data) {
printf("new key is's smaller than original value");
return;
}
x->data = key;
FiboNode *y = x->p;
// if x->data >= y->data, do nothing
// else we need cut
if (y != NULL && x->data < y->data) {
cut(h, x, y);
cascadingCut(h, y);
}
// get min node of this heap
if (x->data < h->min->data) {
h->min = x;
}
}
// if we cut node x from y, it indicates root list of
// h not null
void cut(FibonacciHeap h, FiboNode *x, FiboNode *y) {
// remove x from child list of y, decrementing degree[y]
if(y->degree == 1) {
y->child = NULL;
} else {
x->left->right = x->right;
x->right->left = x->left;
// update child[y]
y->child = x->right;
}
// add x to the root list of h
x->left = h->min;
x->right = h->min->right;
h->min->right = x;
x->right->left = x;
// updating p[x], marked[x] and decrementing degree[y]
x->p = NULL;
x->marked = FALSE;
y->degree -= 1;
}
void cascadingCut(FibonacciHeap h, FiboNode *y) {
FiboNode *z = y->p;
if (z != NULL) {
if (y->marked == FALSE) {
y->marked = TRUE;
} else {
cut(h, y, z);
cascadingCut(h, z);
}
}
}
通过上面的发现我们知道:
1)当我们降低节点x的值时,值一定要小于当前节点x的值
2)若节点x的值比其父亲要小,则已经满足不了最小堆性质
3)当不满足最小堆性质的时候,我们要将其与其父亲y切断,并将x添加到根列表中去
4)若x的父亲在失去第一个孩子的时候则要Mark = true,当y失去第2个孩子,则y要和y的父亲z切断,接着这样不断上升操作。
8. 删除
删除的操作比较简单,就两步:1)将节点的值降低到无穷小
2)进行extractMinimum() 操作
void heapDelete(FibonacciHeap h, FiboNode *x) {
heapDecreaseKey(h, x, -10000);
extractMinimum(h);
}
参考:
[1] Thomas H.Cormen、Charles E.Leiserson《算法导论》第三版 第十九章 “斐波那契对” p291
[2] Thomas H.Cormen、Charles E.Leiserson《算法导论》第三版 第十九章 “斐波那契对” p291
[2] Thomas H.Cormen、Charles E.Leiserson《算法导论》第三版 第十九章 “斐波那契对” p295
感谢 Thomas H.Cormen、Charles E.Leiserson等人
若有参考遗漏的请抱歉,也请您留言
若其中有误的请多多留言不甚感激
完整源码,请点击
注:转载请注明