绪论

Fibonacci Heaps和Complete Heaps一样都是优化版本的堆。Complete Heaps是通过完全二叉树的简单性质来避免堆出现糟糕的退化情况,实际上在一般(最优)情况下各操作的复杂度和一般堆是一样的。

Fibonacci Heaps的优化结合了lazy操作(懒惰标记),散列的一些思想和Fibonacci数的数学性质,在树的结构上不要求一昧的维持平衡(可能也维持了,只是我没有理解),在操作上可以将union(合并),insert(插入),decrease-key(修改某个元素的key值)这三个操作的均摊时间复杂度降低到O(1)。

(decrease-key在国内的教材中往往不做介绍,C++编译器的priority_queue也没有提供这个函数)

所以Fibonacci Heaps在使用优先队列需要进行多次队列中key值修改的算法方面可以做到很好的优化,例如Prim算法,不过我们能够找到的用优先队列实现的普利姆算法,往往用另外的操作(详情见其他文章)替代了修改队列中key值的那一步,对于decrease-key操作这方面介绍的就比较少甚至没有。


Fibonacci Heaps

Fibonacci Heaps从结构上看若干个堆组成的森林,各个堆的堆顶也就是所在堆优先级最高(低)的元素构成的链表。

用一个min指针始终指向堆顶结点构成的链表中优先级最高(低)的元素,即整个Fibonacci Heaps优先级最高(低)的元素。

树中有如下属性:

n表示堆中结点的总个数;rank(x)表示结点x的孩子个数,在还没有进行过lazy标记的情况下,我们尽可能地希望链表中结点的rank值各不相同,在每次extract_min操作中进行调整,可以容忍多个结点rank值为0的情况;trees(H)为森林中树的个数,即链表中结点的个数;marks表示堆中进行lazy标记的结点的个数。

lazy标记在decrease-key中应用,标记一些rank值发生变化但不急于调整的结点。


Insert

插入很简单,创建一个新的结点作为树插入到森林中去,因为min指针指向优先级最高的结点,我们可以直接将新插入的树(结点)插入到min指针指向的结点的左右边,然后根据插入数据的情况选择是否修改min指针的指向。

复杂度显然为O(1)。


Union

合并也很简单,就是将两棵森林对应的双链表进行合并,新的min指针指向两个min指针指向元素优先级较高的元素。

和插入一样,复杂度为O(1)。


Extract_min

Extract_min就是删除最小元素,即pop操作。我们从单链表中删除min指向的结点,将min的孩子结点加入到链表中(由此可以看出兄弟结点也构成链表)。

前面提到希望始终保持链表中的rank值各不相同,采取的策略是将上面调整后的链表的元素的rank值遍历一遍(全部遍历一遍,因为会有多个rank值为0的结点),另用一个数组来记录出现过的rank值的元素的索引,如果遍历到一个结点的rank值与前面结点的rank值重复,将前面rank值对应的结点作为子节点插入到现在遍历的结点(反过来插入大概也是可以的,反正就是做一个合并就可以了)。

需要注意的是,插入之后被插入结点的rank值改变,还要重复对改变后的rank值进行检查。


删除结束后,链表各个结点的rank值都不相同。


复杂度分为三个部分:

1.切断min指向结点到子节点的联系,将子节点的双链表插入到森林对应的链表中,复杂度为O(rank)。
2.更新min值的过程和重构森林避免rank值重复的过程都是将新链表的元素遍历一遍,复杂度为O(rank)+O(tree)。

均摊时间复杂度为O(rank)。


decrease-key

很多博客中的一般说法认为优先队列,即堆中出除堆顶元素外的其他元素是访问不了或者说无法直接更改的(例如我曾经写模拟C语言大作业时想用优先队列来存储信息,后来发现无法直接打印)。

事实上如果我们想要在O(1)的复杂度访问到堆顶元素外的其他元素,只需要建立一个一对一的索引数组即可,然后通过索引直接访问结点,修改结点,因为结点值的减小更容易调整(优化?),更经常出现,所以称这个操作为decrease-key,我们后面讨论的结点值的修改都是减小。

如果是索引为数组存储的下标,在进行交换时,索引跟着一同修改。

对于Fibonacci Heaps的decrease-key操作分为以下两大情况:

1.修改后对堆结构不造成影响。

这种情况下只需要对父节点和子节点进行判断,不需要额外的操作。

2.修改后的key使得堆的结构被破坏。

此时直接将修改结点以及以它为根节点的子树插入到森林对应的链表中。

失去子节点的父节点rank值发生改变,如果是第一次发生改变,我们认为不会破坏结构(或者破坏的较小),只对他进行标记,不调整结构。


如果父节点的rank值在之前已经发生过改变,即marked的状态,就需要将它放回到链表中,然后取消标记。


父节点的父节点也需要进行相同的考虑,递归到最上层或者未标记结点结束。


这么做均摊的时间复杂度为O(1)(因为lazy标记,第三种情况发生的概率占比不高)。


Delete

delete就是删除任意的一个元素,删除的操作为extract_min和decrease_key的结合,先用decrease-key将被删除元素的key值减小到0,此时它一定被调整到森林对应的链表位置,且值最小,再进行一次extract_min即可。

复杂度为两次操作的和。


Key Lemma

应该就是支撑Fibonacci Heaps的关键数学结论(lemma不是常见词汇emmm)。

我们前面讨论到各个操作的复杂度,其中extract-min的复杂度为O(rank),那么会不会出现rank值爆炸的情况呢?

有数学结论,链表中结点rank值最大值的复杂度为logn级别的。

和红黑树,complete heaps的最坏情况一样,Fibonacci Heaps的最坏情况也是根据规模递归建立(将前前个最坏情况的树作为子树插入到前一个最坏情况对应树的根结点上)。


于是最坏情况和结点个数呈斐波那契数列的关系,斐波那契数列的表达式可以推出。



总结

数学好才是真的好。

Fibonacci Heaps相关推荐

  1. 斐波那契堆(Fibonacci heaps)

    一:斐波那契堆 1:特性 斐波那契堆同二项堆一样,也是一种可合并堆.斐波那契堆的优势是:不涉及删除元素的操作仅需要O(1)的平摊运行时间(关于平摊分析的知识建议看<算法导论>第17章).和 ...

  2. Programmer Competency Matrix

    [原文:http://www.indiangeek.net/wp-content/uploads/Programmer%20competency%20matrix.htm] [译文:http://st ...

  3. Stanford University courses of computer science department(斯坦福计算机系课程设置)

    斯坦福学科目前分为7个department:Business, Earth, Education, Engineering, Humanities & Sciences, Law, Medic ...

  4. 一些鲜为人知却非常实用的数据结构 - Haippy

    原文:http://www.udpwork.com/item/9932.html 作为程序猿(媛),你必须熟知一些常见的数据结构,比如栈.队列.字符串.链表.二叉树.哈希,但是除了这些常见的数据结构以 ...

  5. 《算法导论3rd第十九章》斐波那契堆

    前言 第六章堆排序使用了普通的二叉堆性质.其基本操作性能相当好,但union性能相当差. 对于一些图算法问题,EXTRACT-MIN 和DELETE操作次数远远小于DECREASE-KEY.因此有了斐 ...

  6. 斯坦福大学计算机类课程视频

    斯坦福大学计算机类课程都是以CS开头编号,可以在网址https://exploredegrees.stanford.edu/coursedescriptions/cs/查询,在网上可以登录查看课程的课 ...

  7. Lecture 21

    绪论 前几章节我们介绍了图中的一系列知识点--基础术语(有向,无向,边,顶点,度)和存储方式(邻接矩阵,邻接表),遍历结点的方式(宽度优先遍历和深度优先遍历),拓扑排序(我个人觉得应该将拓扑排序看作图 ...

  8. Lecture 17-2

    绪论 上一章节介绍了图的遍历,这一章节开始介绍图中生成树,最小生成树的概念以及生成最小生成树的算法. Definition and Application 首先还是要知道问题和算法涉及概念的定义是什么 ...

  9. 【一看就懂】数据结构以及各种算法的可视化演示工具

    文章目录 大家好,我是只谈技术不剪发的 Tony 老师. 最近发现了一个宝藏网站:Data Structure Visualizations,提供了一个在线的可视化工具,可以交互式地演示各种数据结构和 ...

  10. Fibonacci数列的java实现

    关于Fibonacci应该都比较熟悉,0,1,1,2,3..... 基本公式为f(n) = f(n-1) + f(n-2); f(0) = 0; f(1) =1; 方法1:可以运用迭代的方法实现: p ...

最新文章

  1. ngx_lua与go高并发性能对比
  2. SDNU 1481.纪念品分组(水题)
  3. vue组件的实例使用
  4. 高可用集群 heartbeatv1实例
  5. 火狐浏览器中打开java_将Firefox浏览器嵌入Java Swing中
  6. java调用js查询mongo_MongoDB增删查改操作示例【基于JavaScript Shell】
  7. python list的+=操作
  8. C++中的向量vector
  9. 深度学习--二值神经网络BNN基础概念学习总结+官方代码解析
  10. spss分析方法-相关分析(转载)
  11. 【十大IDE】 解决你不懂英文的痛苦
  12. c语言数字拆分,在手机上玩C语言—数字拆分
  13. PCB设计之EMC 47原则
  14. 《95后的指数基金投资课》进阶阶段:估值判断之最基本的估值指标-市盈率PE,市净率PB,股息率
  15. char* 和 char[]区别
  16. vue实现刷新页面随机切换背景图【适用于登陆界面】
  17. 双离合档把上按钮作用_自动挡挂档要按按钮吗 主要为了防止挡误操作
  18. 论文阅读: BotCamp: Bot-driven Interactions in Social Campaigns WWW 2019
  19. Linux之用户授权及权限安全
  20. php如何读取文件,PHP如何读取文件内容?,懂得这些技巧就够了

热门文章

  1. 【卸载双系统中的linux系统】删除引导
  2. 解决Mac自动切换输入法
  3. java 右对齐_字符串对齐器(左对齐、居中、右对齐)
  4. win10无法新建文件夹怎么办?(已解决)
  5. 资讯--2019年4月
  6. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java学生综合考评系统b8vlm
  7. Java 嵌入 SPL 轻松实现 Excel 文件合并
  8. STM32入门之GPIO详解
  9. uniapp map 点聚合
  10. win10c盘扩容_如何给磁盘进行扩容/拆分/合并的操作?保姆级教学