优先队列——斐波那契堆(without source code)

来源:互联网 发布:win10 软件消失了 编辑:程序博客网 时间:2024/05/18 15:04

【0】README

0.1) 本文部分内容转自 数据结构与算法分析,旨在理解 斐波那契堆 的基础知识;
0.2) 文本旨在理清 斐波那契堆的 核心idea,还没有写出源代码实现,表遗憾;
0.3)从实际角度看: 除了某些需要管理大量数据的应用外,对于大多数应用,斐波那契堆的常数因子和编程复杂性使得它比起普通二项堆(二项队列)并不是那样适用;
0.4)斐波那契堆(Fibonacci heap)通过添加两个新的观念推广了二项队列(Notions): (干货——斐波那契堆通过添加两个新的观念推广了二项队列(Notions))

  • N1)DecreaseKey 的一种不同的实现方法:以前的方法是把元素朝向根节点上滤。对于这种方法似乎没有理由期望O(1)的摊还时间界,故需要新的方法;
  • N2)懒惰合并(lazy merging): 只有当两个堆需要合并的时候才 合并。 对于懒惰合并,merge是低廉的, 但是因为懒惰合并并不实际把树结合到一起,所以deleteMin 操作可能会遇到许多的树, 从而使这种操作的代码高昂; 任何一次 deleteMin 都可能花费线性时间, 但是总能够把时间归咎到前面的一些 merge操作中去。特别地, 一次昂贵的deleteMin 必须在其前面要有大量的非常低廉的 merge操作, 它们能够存储额外的位势;(干货——懒惰合并定义)

【1】切除左式堆中的节点

1.1)problem+solution:

  • 1.1.1)problem:如果代表优先队列的树不具有 O(logN)的深度,那么这种方法不适用。例如,若将这种方法用于左式堆,则 decreaseKey 操作可能花费 Θ(N)时间, 如下图所示:
    这里写图片描述
  • 1.1.2)solution: 我们不想把0 上滤到根,因为正如我们看到的那样,存在一些情况使得这样做代价太大。解决方法是把堆沿虚线 切开,如此得到两棵树,然后再把这两棵树合并为一颗树。

1.2)具体操作

  • 1.2.1)令 X 为 要执行decreaseKey 操作的节点, 令 P 为它的父节点;在切断以后,我们得到两棵树,即根为X的 H1 和 T2, T2 是原来的树除去 H1 后 得到的树;

1.3)problem+solution:

  • 1.3.1)problem:如果这两棵树都是左式堆,那么它们可以以时间O(logN)合并完成了。但问题是, T2 未必是左式堆;
  • 1.3.2)solution: 容易恢复左式堆的性质,这要用到下列两个观察到的结论(Conclusions):

    • C1)只有从P 到 T2 的根的路径上的节点可能破坏左式堆的性质, 它们可以通过变换子节点来调整;
    • C2)由于最大右路径最多有 」log(N)+1」个节点,因此我们只需要检查从P 到 T2 的根的路径上的 前 」log(N)+1」 个节点; 将T2 转换为 左式堆H2 后的结构如图11-13所示:

1.4)因为 我们能够以 O(logN) 步将T2 转变成左式堆H2, 然后合并H1 和 H2, 所以得到一个在左式堆中执行 decreaseKey 的O(logN) 算法;合并后的最后结果如图11-14所示:

这里写图片描述


【2】二项队列的懒惰合并

2.1)由斐波那契堆所使用的第二个想法是懒惰合并: 我们将把这个想法用于二项队列并证明执行一次merge操作(还有插入操作,它是一种特殊操作)的摊还时间为 O(1)。对于 deleteMin操作,其摊还时间仍然是 O(logN); (干货——由斐波那契堆所使用的第二个想法是懒惰合并)
2.2)懒惰合并定义: 为了合并两个二项队列, 只要把两个二项树的表连在一起,结果得到一个新的二项队列。这个新的二项队列可能含有相同大小的多棵树,因此破坏二项队列的性质。为了保持一致性,我们将把它叫做 懒惰二项队列(lazy nominal queue)。这是一种快速操作, 该操作总是花费常数时间。和前面一样, 一次插入通过创建一个单节点二项队列并将其合并而完成。 区别在于合并是懒惰的 。 (干货——懒惰合并定义 + 懒惰二项队列定义)
Attention)懒惰二项队列和标准二项队列的区别(Differentiation):

  • D1)懒惰二项队列: 允许有高度相同的二项树存在该队列中;
  • D2)标准二项队列: 不允许有高度相同的二项树存在该队列中;

2.3)deleteMin 操作要麻烦得多, 因为此处需要我们最终把懒惰二项队列转换为 标准的二项队列, 不过,它仍然花费O(logN)的摊还时间——而不像以前是O(logN)最坏情形时间。为了执行 deleteMin , 我们找出(并最终返回)最小元素。如前所述,我们将它从队列中删除, 使得它的每一个子节点都成为一颗新的 树。此时我们通过合并两颗相等大小的树直至不再可能合并为止而把所有的树合并成一个二项队列;

  • Attention)什么时候需要将懒惰二项队列进行合并? (干货——什么时候需要将懒惰二项队列进行合并?)
    • A1)懒惰二项队列的定义: 是在需要的时候才进行合并;不需要的时候不进行合并,即只是将二项树的表(根)连在一起而已;
    • A2)懒惰二项队列需要合并的时候是: 当在执行 deleteMin 操作之后 ,需要将 懒惰二项队列进行真正的合并;(after deleteMin operation)

2.4)看个荔枝:

  • 2.4.1)懒惰二项队列如图11-15 所示:
    这里写图片描述

  • 2.4.2)为了执行deleteMin, 我们删除最小元素,得到的懒惰二项队列如图11-16所示:
    这里写图片描述

  • 2.4.3)现在我们必须将所有的树合并而得到一个标准的二项队列:使用的算法伪代码如下:
    这里写图片描述

  • 对上述算法的分析(Analysis): 上述的 for 循环计数 和 while 循环末尾的检测花费 O(logN) 时间, 这使得运行时间成为所要要求的 O(T+logN)。

  • 2.4.4)图11-18 显示该算法对前面二项树的集合的执行情况:
    这里写图片描述

2.5)懒惰二项队列的摊还分析

  • 定理11.3: merge 和 insert 的摊还运行时间对于懒惰二项队列均为 O(1), 而deleteMin的摊还运行时间为 O(logN);

【3】斐波那契堆操作(注意区分deleteMin 和 decreaseKey 操作)

3.1)正如我们前面提到的那样: 斐波那契堆将左式堆 decreaseKey 操作与懒惰二项队列merge操作结合起来。不过,我们不能一点都不修改。 (干货——斐波那契堆的核心思想)

  • 3.1.1)问题在于: 如果在这些二项树中进行任意切割,那么结果得到的森林将不再是二项树的集合了。因此,每一颗树的秩最多为 」logN」 将不再成立;
  • 3.1.2)由于 在懒惰二项队列中 deleteMin 的摊还时间已被证明是 2logN + R,因此,对于deleteMin 的界,我们需要 R = O(logN)成立;

3.2) 为了保证R=O(logN), 我们对所有的非根节点应用下述法则(rules):

  • r1)将第一次(因为切除而)失去一个子节点的(非根)节点做上标记;
  • r2)如果被标记的节点又失去另外一个儿子节点, 那么将其从它的父节点切除。这个节点现在变成了一颗分离的树的根并且不再被标记。这叫做一次 级联切除,因为在一次 decreaseKey 操作中可能出现多次这种切除; (干货——级联切除定义)

3.2)看个荔枝(注意和上述变换法则相结合):

  • 3.2.1)图11-19 显示了在 decreaseKey 之前斐波那契堆中的一颗树(被标记的节点有: 8,11, 10, 17, 33)。 (干货——现在讲的操作是 decreaseKey)
    这里写图片描述

    • step1)当关键字为 39 的节点变为12的 时候,堆序被破坏;
    • step2)由于包含33的节点已经被标记了(在对39进行decrease之前),那么这是它的第二个失去的子节点,从而它也被从它的父节点(10)中切除;(2nd rule applied)
    • step3) 10也失去了它的第二个儿子, 于是10 从 根为5的树中被切除;(2nd rule applied)
    • step4)切除过程结束,因为 5 是 未做标记的。现在把 节点5 做上标记, 如图11-20所示:(1st rule applied)

Attention)过去被做过标记的节点10 和33 不再被标记, 因为现在他们都是根节点, 这在时间界的证明中是极其重要的;(Attention——过去被做过标记的节点10 和33 不再被标记, 因为现在他们都是根节点。)
这里写图片描述

补充:intro to 斐波那契堆(转自http://dsqiu.iteye.com/blog/1714961)

  • 斐波那契堆(Fibonacci Heap):斐波那契堆是一种松散的二项堆,与二项堆的主要区别在于构成斐波那契堆得树可以不是二项树,并且这些树的根排列是无须的(二项堆的根结点排序是按照结点个数排序的,不是按照根结点的大小)。斐波那契堆得优势在于它对建堆、插入、抽取最小关键字、联合等操作能在O(1)的时间内完成(不涉及删除元素的操作仅需要O(1))。这是对二项堆效率的巨大改善。但由于斐波那契堆得常数因子以及程序设计上的复杂度,使它不如通常的二叉堆合适。因此,它的价值仅存在于理论意义上。
  • 斐波那契堆的另一个特点就是: 合并操作只发生在抽取一个结点之后,也就是说斐波那契堆的维护总是会延后的(个人根据代码理解的)。 (干货——斐波那契堆与二项堆(二项队列)的区别)

【4】时间界的证明

  • 定理11.4:斐波那契堆对于 insert,merge 和 decreaseKey 的摊还时间界均为 O(1), 而对于deleteMin 则是 O(logN);
0 0