斐波那契堆(Fibonacci Heap)

来源:互联网 发布:京东众包软件打不开 编辑:程序博客网 时间:2024/05/21 15:31

也许我们每个人都知道斐波那契数列(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 heaptypedef 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 nodeFiboNode * 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 heaptypedef 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 itFibonacciHeap 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 heapFibonacciHeap 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 heapFibonacciHeap 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 heapFiboNode * 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 xvoid 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 keyvoid 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 nullvoid 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等人
若有参考遗漏的请抱歉,也请您留言
若其中有误的请多多留言不甚感激


完整源码,请点击





注:转载请注明





0 0
原创粉丝点击