斐波那契堆(Fibonacci Heap)

本文深入探讨了斐波那契堆的数据结构及其关键操作,包括插入、合并、抽取最小节点等,并提供了详细的伪代码实现。

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


完整源码,请点击





注:转载请注明





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值