[CLRS][CH 19]斐波那契堆

斐波那契堆简介

斐波那契堆(Fibnacci Heap)有两种用途:第一,支持一系列操作,这些操作构成了所谓的可合并堆。第二,其一些操作可以在常数时间内完成,这使得这种数据结构非常适合于需要频繁调用这些操作的应用。

可合并堆(Mergeable Heap)支持如下五种操作:Make-Heap(), Insert(H, x), Minmun(H), Extract-Min(H), Union(H1, H2)。事实上,就是具备了快速合并操作的堆(Heap)。

斐波那契堆还支持额外两种操作:Decrease-Key(H, x, k), Delete(H, x)。

斐波那契堆结构

一个斐波那契堆是一系列具有最小堆序的有根树集合,每棵树都遵循最小堆性质。如图:

堆属性:

每个结点 x 包含一个指向其父节点的指针 x.parent 和一个指向其某一个孩子的指针 x.child,x 的所有构成了一个双向环形链表,称之为 x 的孩子链表。每个孩子节点 y 包含指针 y.left 和 y.right 指向兄弟节点。孩子链表中各兄弟出现的次序是任意的。

每个结点还有两个属性。把结点 x 的孩子链表中的孩子数目存储在 x.degree 中,布尔值属性 x.mark 指示结点 x 自上一次成为另一个结点的孩子后,是否是去过孩子。新节点是未被标记的,当结点 x 成为另一个结点的孩子时,便成为未被标记结点。

通过指针 H.min 来访问一个给定的斐波那契堆 H,指向该堆的最小根节点。如果堆为空,则 H.min 指向 NULL。因为根节点也构成了双向环形链表,所以根节点的次序是任意的。

根节点还有一个属性 H.n 表示当前节点数目。

势函数:

通过势函数来分析斐波那契堆性能。对于给定堆 H, 用 t(H) 表示 H 中根链表中树的树木,用 m(H) 来表示 H 中已标记结点的树木。定义斐波那契堆 H 的势函数 Φ(H) = t(H) + 2m(H)。

最大度数:

在一个 n 个结点的斐波那契堆中任何结点的最大度数都有上界 D(n)。如果仅仅支持可合并堆操作,那么 D(n) ≤ floor(lgn),当支持斐波那契堆操作时,也要求 D(n) = O(lgn)。

可合并堆操作

斐波那契堆上的一些可合并操作应尽可能延后执行。不同的操作可以进行性能平衡。例如,当执行 Extract-Min 操作后,不得不遍历根链表剩下的结点找出新的最小结点。这里便存在性能平衡问题。只要我们在执行 Extract-Min 操作后便利整个根链表,并把结点合并到最小堆序树中以减小根链表的规模。后面将讨论到,不论执行 Extract-Min 之前是什么样子,执行之后,根链表中每个节点要求有一个唯一的度数,使得根链表规模最大为 D(n)+1。

创建新的斐波那契堆:

创建一个新堆,Make-FibHeap 过程分配并返回一个斐波那契堆对象 H,其中 H.n = 0 和 H.min = NULL,H 中没有树,因为 t(H) = 0 且 m(H) = 0,所以其势 Φ(H) = 0。因此其摊还代价等于其实际代价 O(1)。

插入一个节点:

下面过程将结点 x 插入至 H 中,其中 x.key 已被赋值。

 FibHeap-Insert(List *H, Node *x) {
     x.degree = 0
     x.parent = NULL
     x.child = NULL
     x.mark = False
     if (H.min == NULL)
         create a root list for H containing just x
         H.min = x
    else
        insert x into H‘s root list
        if (x.key < H.min.key)
            H.min = x
    H.n++
}

势的增加量为 1,实际代价为 O(1),所以摊还代价为 O(1) + 1 = O(1)。

寻找最小节点:

可以通过指针 H.min 直接得到,摊还代价等于实际代价为 O(1)。

两个斐波那契堆合并:

该过程简单地将 H1 和 H2 的根链表链接,然后确定新的 H.min。

FibHeap-Union(List *H1, List *H2) {
    H = Make-FibHeap()
    H.min = H1.min
    concatenate the root list of H2 with that of H1
    if ((H1.min == NULL) || (H2.min != NULL && H2.min.key < H1.min.key))
        H.min = H2.min
    H.n = H1.n + H2.n
    return H
}

第1~3行将 H1 和 H2 的根链表链接成为 H 的新根链表。第2~5行设定 H 的最小根节点。第6行设置合并后的节点数目。

势的变化为 0,所以摊还代价等于实际代价 O(1)。

抽取最小结点:

该过程首先将最小节点 H.min 的每个孩子变成根节点,并从根链表中删除该结点。然后合并相同度数结点,优化根链表(这一步操作一直延迟到 Extract-Min 后才执行)。

FibHeap-Extract-Min(List *H) {
    z = H.min
    if (z != NULL)
        for each child x of z
            add x to the root list of H
            x.parent = NULL
        remove z from the root list of H
        if (z == z.right)
            H.min = NULL
        else
            H.min = z.right
            Consolidate(H)
        H.n--
    return z
}

下一步要合并 H 的根链表,通过调用 Consolidate(H) 来减少根链表树的树木。合并过程重复下列过程,直到每个根都具有不同的度数:
  1. 在根链表中找到两个相同度数的根 x 和 y,假定 x.key ≤ y.key;
  2. 把 y 链接到 x:从根链表移除 y,调用 FibHeap-Link 过程,使 y 成为 x 的孩子。该过程将 x.degree 增加1,并清除 y 上的标记。

过程 Consolidate(H) 使用一个辅助数组 A[0..D(H.n)] 来记录根节点对应度数的轨迹。如果 A[i]=y,那么当前的 y 是一个具有 y.degree=1 的根。计算 D(H.n) 的过程留到后面讲解。

Consolidate(List *H) {
    create array A[0..D(H,n)]
    for i = (0 to D(H,n))
        A[i] = NULL
    for each node w in the root list of H
        x = w
        d = x.degree
        while (A[d] != NULL)
            y = A[d]
            if (x.key > y.key)
                exchange x with y
            FibHeap-Link(H, y, x)
            A[d] = NULL
            d++
    H.min = NULL
    for i = (0 to D(H,n))
        if A[i] != NULL
            if H.min == NULL
            create a root list for H containing just A[i]
        else
            insert A[i] into H‘s root list
            if A[i].key < H.min.key
                H.min = A[i]
}

FibHeap-Link(List *H, Node *y, Node *x) {
    remove y from the root list of H
    make y a child of x, incrementing x.degree
    y.mark = False
}

抽取最小节点的摊还代价为 O(lgn)。

斐波那契堆操作-降权和删除

关键字降权:

关键字降权的时候,如果被降权的结点是一个根节点的子节点,则需要判断降权后是否违反最小堆性质。如果子节点比根节点小,则直接将子结点树剪切至斐波那契堆根链表中。如果被剪切子节点的父节点没有标记,则打上标记。如果有标记,则将其父节点也剪切至根链表中,这称之为级联剪切。级联剪切一直向上回溯,直至遇到一个未被标记结点或者根节点。

FibHeap-Decrease-Key(List *H, Node *x, int key) {
    if (key > x.key)
        error "New key is greater than current one."
    x.key = key
    y = x.parent
    if (y != NULL && x.key < y.key)
        Cut(H, x, y)
        Cacsading-Cut(H, y)
    if (x.key < H.min.key)
        H.min = x.key
}
Cut(List *H, Node *x, Node *y) {
    remove x from the child list of y, y.degree--
    add x to the root list of H
    x.parent = NULL
    x.mark = False
}
Cascading-Cut(List *H, Node *y) {
    z = y.parent
    if (z != NULL)
        if (y.mark == False)
            y.mark = True
        else
            Cut(H, y, z)
            Cascading-Cut(H, z)
}

关键字降权的摊还代价至多为 O(1)。之所以之前有2倍于节点数目的势,一个单位的势支付切断和标记位的清除,另一个单位补偿了被剪切结点成为根节点后多出来的势。

删除节点:

非常简单,降权至-∞后直接 Extract 出去。

FibHeap-Delete(List *H, Node *x) {
    FibHeap-Decrease-Key(H, x, -∞)
    FibHeap-Extract-Min(H)
}

斐波那契堆理论分析

对于斐波那契堆中的每个结点 x,定义 size(x) 为以 x 为根的子树中包括 x 本身在内的结点个数。我们将证明 size(x) 是 x.degree 的幂。

引理1:设 x 是斐波那契堆中的任意结点,假定 x.degree = k。设 y1, y2, ..., yk 表示 x 的孩子,并以他们链入 x 的先后顺序排列,则 y1.degree ≥ 0,且对于 i = 2, 3, ..., k,有 yi.degree ≥ i-2。

引理2:对于所有整数 k ≥ 0,Fibk+2 = 1 + sigma(i = 0, k)Fibi

引理3:对于所有整数 k ≥ 0,斐波那契数的第 k+2 个数满足 Fibk+2 ≥ φk,其中 φ = (1 + 根号5)/2。

引理4:设 x 是斐波那契堆中的任意结点,并设 k = x.degree,则有 size(x) ≥ Fibk+2 ≥ φk

引理5:一个 n 个节点的斐波那契堆中任意结点的最大度数 D(n) = O(lgn)。

时间: 2024-08-07 08:23:17

[CLRS][CH 19]斐波那契堆的相关文章

算法导论 第20章 斐波那契堆

一.概念 1.斐波那契堆 斐波那契堆是可合并堆 在不涉及删除的操作(除去EXTRACT和DELETE)中,操作仅需O(1)的平摊运行时间 当EXTRACT和DELETE的操作数目较小时斐波那契堆能得到较好的运行效率. 斐波那契堆不能有效地支持SEARCH操作 用于解决诸如最小生成树和寻找单源最短路径等问题的快速算法都要用到斐波那契堆. 2.斐波那契堆的结构 斐波那契堆由一组最小堆构成,这些最小堆是有根的无序树. 结点结构: key: 关键字,作为排序.判断结点大小的标准 left, right:

笔试算法题(46):简介 - 二叉堆 &amp; 二项树 &amp; 二项堆 &amp; 斐波那契堆

二叉堆(Binary Heap) 二叉堆是完全二叉树(或者近似完全二叉树):其满足堆的特性:父节点的值>=(<=)任何一个子节点的键值,并且每个左子树或者右子树都是一 个二叉堆(最小堆或者最大堆):一般使用数组构建二叉堆,对于array[i]而言,其左子节点为array[2*i],其右子节点为 array[2*i+1]:二叉堆支持插入,删除,查找最大(最小)键值的操作,但是合并二叉堆的复杂度较高,时间复杂度为O(N):但是二项堆或者斐波 那契堆则仅需要O(logN): 二项树(Binomial

golang 实现斐波那契堆

二叉堆提供了o(lgn) 时间的插入, 删除最小,降级等操作,o(n) 时间的合并操作;  斐波那契堆提供了更优操作时间界限:o(1) 插入, o(lgn) 删除最小, o(lgn) 删除, o(1)合并. 根据算法导论上说,斐波那契堆在删除最小或删除操作被执行次数相比其他操作少得多时,尤为适用.一些图的算法中(计算最小生成树,单源最短路径)作为基本构建块(作为优先队用). 考虑到斐波那契堆实现的复杂,可能二叉堆在实践上更具可用性.就像rob pike 在 <notes on programmi

算法导论第十九章 斐波那契堆

<算法导论>第二版中在讨论斐波那契堆之前还讨论了二项堆,但是第三版中已经把这块的内容放到思考题中,究极原因我想大概是二项堆只是个引子,目的是为了引出斐波那契堆,便于理解,而且许多经典的算法实现都是基于斐波那契堆,譬如计算最小生成树问题和寻找单源最短路径问题等,此时再把二项堆单独作为一章来讲显然没有必要.类似的堆结构还有很多,如左倾堆,斜堆,二项堆等,下次我打算开一篇博客来记录下它们的异同点. 一.摊还分析(第十七章) 这些高级的数据结构的性能分析一般是基于一个技术——摊还分析,可以理解成一种时

斐波那契堆

斐波纳契堆(Fibonacci Heap)于 1984 年由 Michael L. Fredman 与 Robert E. Tarjan 提出,1987 年公开发表,名字来源于运行时分析所使用的斐波那契数. 斐波那契堆同二项堆(Binomial Heap)一样,也是一种可合并堆(Mergeable Heap).与二项堆一样,斐波那契堆是由一组最小堆有序树构成,但堆中的树并不一定是二项树.与二项堆中树都是有序的不同,斐波那契堆中的树都是有根而无序的. 实际上,斐波那契堆松散地基于二项堆.如果不对斐

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

class Node(object):     __slots__ = [         'data', 'child', 'left', 'right',         'degree', 'parent', 'childCut',     ]     def __init__(self, data):         self.data = data         self.child = None         self.left = None         self.right

优先队列——斐波那契堆

1. 引言 最近一直在写最短路径的迪杰斯特拉与双向迪杰斯特拉算法,使用优先队列可以极大的加快算法的运行效率.比如在OL数据集中,对于迪杰斯特拉算法用优先队列(二叉堆实现)代替普通的数组(数据结构书中提供的算法)快了将近60倍. 由上可得如何实现优先队列对迪杰斯特拉.双向迪杰斯特拉以及其它用到优先队列的最短路径求解算法(如reach.A*)等至关重要.另外对于一些其他用到优先队列的问题也具有相当的影响. 对于优先队列来说,只需要入队.出队即可,因为该文章只关注堆的插入(push)与删除(delet

算法导论 第十九章:斐波拉契堆

斐波拉契堆是由一组最小堆有序树组成,每棵树遵循最小堆性质,并且每棵树都是有根而无序的.所有树的根通过left和right指针来形成一个环形的双链表,称为该堆的根表. 对于一个给定的斐波拉契堆H ,可以通过指向包含最小关键字的树根指针H.min来访问.堆中每个节点还包含x.mark,x.degree两个域,x.degree表示x的子女表中的子女个数:x.mark表示从x上次成为另一个节点子女以来是否失掉一个孩子. 斐波拉契对的结构如下: 势能函数: 可以利用势能方法来分析斐波拉契堆的性能.其势能函

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

概要 本章介绍斐波那契堆.和以往一样,本文会先对斐波那契堆的理论知识进行简单介绍,然后给出C语言的实现.后续再分别给出C++和Java版本的实现:实现的语言虽不同,但是原理如出一辙,选择其中之一进行了解即可.若文章有错误或不足的地方,请不吝指出! 目录1. 斐波那契堆的介绍2. 斐波那契堆的基本操作3. 斐波那契堆的C实现(完整源码)4. 斐波那契堆的C测试程序 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3659060.html 更多内容:数据结