堆的进化之旅4-Fibonacci Heap斐波拉契堆


堆的进化之旅的分支——斐波拉契堆。
为记录算法研讨课精彩报告而生。
堆有序按照小顶堆来,即父亲节点的关键字要比孩子节点的要小

Fibonacci Heap

我们首先来总结一下前面提到的三种数据结构的时间复杂度,在每个操作步骤中,如果我们都取最小复杂度,那么push操作只要O(1),pop_min操作只要O(log n),decrease_key操作只要O(1),多么希望能有集各种有点于一身的一种数据结构啊,那有没有一种这样的数据结构满足要求呢?
通过阅读论文,我们得知Michale L·Fredman在1987年创造性的实现了这一构想,即斐波那契堆这一数据结构,它起源于1978年,经Jean Vuillemin提出的二项式堆。

时间复杂度

斐波拉契堆

OperationLinked ListBinary HeapBinomial HeapFibonacci Heap
PushO(1)O(log n)O(1) amortizedO(1) amortized
Pop_minO(n)O(log n)O(log n)O(log n) amortized
Decrease_keyO(1)O(log n)O(log n)O(1) amortized

Push

它摒弃了在二项式堆的push和decrease_key这两阶段的merge操作,把merge操作全部留到pop_min操作当中,这使得斐波那契堆和二项式堆有不同的形状。此即lazy原则,斐波那契堆不是在可合并树时就进行合并,而是在积累到一定量时才合并,结果表面的lazy反而最终提升了清理效率。最终push操作插入和更新都是O(1), O(1)+ O(1)最终push操作时间复杂度还是O(1)。
在这里插入图片描述
Push : 它自带一个指向最小根节点的指针,把5作为一个单节点的树插入到根列表的末尾,并与旧的最小根节点作比较,更新。对顶点2也一样。

Pop_min

Delete

Promote children

再看pop_min操作:根据最小元素指针弹出最小元素后,其孩子节点和二项式堆中的处理一样,直接promote到根链表上。然后遍历根列表,若发现存在度数相同的两棵树,则进行合并操作。
在这里插入图片描述
Pop_min : 删除最小根节点1之后,将它的孩子子树:4, 3提升到根列表上来,接着合并度数相同的树,顶点7和3合并,再是以顶点4、3为根节点的树来合并,最后是顶点5、2。

Merge

我们仔细来看这一步中结合即Merge在论文中是如何具体操作的:论文中提出,借助root_array这一根节点数组,数组序号即代表树度数,此数组相当于一排容量仅为1的桶,把树依次进桶,若出现两颗树进入同一个桶,即两树根节点有相同的度数,立即合并。最后合并好后重新回到根链表上。
在这里插入图片描述
从头遍历根列表,以顶点7为根节点的树度数为0,扔进0号桶,以顶点7为根节点的树扔进1号桶。对于以顶点3为根节点的树,0号桶已经满了,说明存在度数相同的树,那么合并它们,比较根节点大小,小的作为新的父亲节点。一直遍历到最后,只剩下两颗树,这个时候就把它们搬回根列表当中,合并操作完毕。

Decrease key

最后我们来看斐波那契堆中的Decreasekey操作,当更新节点键值之后,如果出现不符合父节点键值小于等于子节点键值时,不像二项式堆中向上bubble,而是从原树上分割下来,直接接到根链表上。
在这里插入图片描述
Decrease key : 0 -> 1 : 顶点5的关键字减小为3,仍符合堆有序,不需要做什么;1 -> 2 : 3到0,那么就把顶点0丢到根列表上;2b -> 3 : 顶点7的关键字减小为1,将以新生成的顶点1为根节点的树扔到根列表中(那个棕色记号马上解释,还有3c)

1987年Michale L·Fredman提出的斐波那契堆的另一个特点就在这张图上。之前说到在pop_min操作中根节点弹出后斐波堆和二项式堆都是直接接到根链表上,但如果出现白底图中这种极度扁平的树,孩子节点超级多,就会消耗很多的时间。斐波堆为了优化这种情况,使用特有的标记(mark)loser操作。此操作实现在decreasekey阶段,Rule1:损失一个孩子节点,即这个孩子节点切掉接到根链表上时,把这个父节点标记为loser。大家可以认为,连孩子都弄丢了,这父母不肯定就是个loser吗?Rule2:当一个被标记为loser的父节点再损失一个孩子,即这个父节点损失了两个孩子时,他就无颜再留在这个树当中了,切掉这个节点的整个子树,把他也接到根节点链表上。最终,我们通过这两个规矩标记loser、切割节点,就避免了扁平化的树,根节点就不会再拥有太多的孩子节点,提高了许多效率。1987年的新数据结构如果没有这个操作,最终它的平摊分析时间复杂度就不会有如此大的提升。下方的图是一个例子演示。在这里插入图片描述
首先顶点5,8已经标记为成了Loser,减少顶点9的关键字,把6扔到根列表中,这是顶点8丢失了第二个孩子,就把顶点8也丢上去,并且清除Loser标记。这样顶点5也丢了第二个孩子,重复操作,知道遇到一个正常的顶点:4,标记为Loser,随即停止操作。

Amortized time

不知道大家注意到之前时间复杂度对比图中的Amortized alanysis没有。它代表平摊分析,帮助我们对算法复杂度上界更准确的分析。过往我们把每个操作都用最坏情况来得出算法复 杂度理论上界,这往往与实际复杂度上界存在较大误差,因为一连串的操作往往具备内在关系,不会都在最坏情况下运行,于是我们使用平摊分析。在平摊分析中,执行一系列数据结构操作所需要的时间是通过执行的所有操作求平均而得出的,这样得出的复杂度代价理论上界和实际代价上界更接近。平摊分析可以用来证明在一系列操作中,通过对所有操作求平均之后,即使其中单一的操作具有较大的代价,平均代价还是很小。实际过程中,我们经常使用聚集分析、记账方法和势能方法来实现平摊分析。在斐波那契堆复杂度分析中我们使用势能方法中,因为每种操作可能有不同的平摊代价,我们就用一种势函数Φ(Di)一一表示映射当前这一操作对应的势能。我们来看图中的平摊代价公式:ai= ci +Φ(Di) - Φ(Di-1),ai是平摊代价,ci是实际代价,累加n个操作的总的平摊代价为就为图中的 ∑ai= ∑ci+Φ(Dn)- Φ(D0),==Φ(Dn)-Φ(D0)==代表总势能差,最终它在需要时可以释放出来我们额外给它赋予的代价,就相当于一些操作我们多给些钱执行,结余的钱用来后面某个操作钱不够时补贴,最终总共花的钱比原来用最坏情况花的钱肯定要少,同时又能满足实际需要。不同的势函数可能会产生不同的平摊代价,但它们都是实际代价的上界。最佳势函数的选择取决于所需的时间界。在这里插入图片描述
了解了平摊分析的含义,我们来看它在分析斐波那契堆时会是什么样.这里的势函数选择 Φ=此时根节点数+两倍的 Loser 节点数。在这里插入图片描述

Pop_min

(1) 删除根节点,提升孩子主要花费在了提升孩子上,因为最多会有d_max个孩子,所以实际时间为O(d_max)。至于势函数的变化,就是树的个数变化了,因为原来孩子子树没有算进去,现在都被提到的根列表上,所以会不大于 d_max,最后代入公式即可。
(2) 我们设最开始的时候有t颗树,做了m次合并,最后剩下来d颗树。首先需要遍历根列表的,需要O(t)。而每一次合并呢,只需要O(1),这里有m次,所以花费了O(m)。那么最后合并结束时需要把剩下来的树丛桶里面搬回去,这里又花费掉了O(d)。这里还有一个关系式t = m + d,因为每一合并会使树的数量减少1,所以m和d之和不变。对于势函数来说,主要是也是根节点的数量变化,原来有t颗树,现在只有d颗树了。

这当中decreasekey操作中父节点键值被更新,它子树里的孩子节点也可能被更新,这就是迭代,产生的额外消耗正巧就用到我们势函数计算产生的势能差,把这部分多付出的时间用上去,释放出来。
这样一计算,我们采用斐波那契堆的数据结构确实和原来相比,把有的操作算法复杂度从O(log n)降到了O(1),有的从O(n)降到O(log n),提高了很多效率。

那其实还有一个问题:斐波那契堆为什么叫斐波那契堆呢?其实它的命名方式和二项式堆来历很相似。我们来看一个斐波那契堆里度数为k的树它最少有几个节点?先大致推断下图中。发现这个斐波那契堆里度数为k的树的节点恰好都大于等于对应斐波那契数列里的数字。
这里的F_d+2指的是斐波拉契数列里面第d+2个数,Φ为黄金数:(√5-1)/2
在这里插入图片描述
我们严格地从数学角度来证明它:
首先介绍一个“grandchildren”定理,这里有两种解释方法:
只看图(先不要看下方文字描述),设堆中树的根节点是x,当度为0时,显然只有0个孩子,把他记作y1树,两个y1树合并成一个度为1的树,在经历了decreasekey后如果变成loser有孩子节点被切掉,那么也就只有0个孩子,记为y2;依次类推,y3的孩子节点是y1和y2,最少有1个孩子。这么一递推,yd的孩子节点就是y1到yd-1,借助数学归纳法yd(d是下标)有的孩子节点数>=d-2。最后总结“grandchildren”定理即度数为d的根节点,把他的孩子用1到d标号,1<=i<=d,则它通过i这个孩子节点拥有的孙子节点>=i-2。
②注意到合并操作只能针对度数相等的树来进行,对顶点x,我们呢按照成为它的孩子的顺序给它们编号为y1, y2, … yd。那么y1和x合并,这个时候都没有各自的孩子,接下来合并y2和x,这时x已经有了1个孩子y1,所以y2也已经带有一个孩子了,但是呢,由于decrease_key操作可能在任意时刻删除掉一个孩子,所以y2可能只有1个孩子,得到y2的孩子数 >= 0,以此类推,得到下方性质。
在这里插入图片描述
有了“grandchildren”定理,我们以此求一个度为d的根节点的树中总共至少有几个节点,用Nd来代表这个值:它有d个孩子节点,编号1到d,那么Nd这个总结点数就等于1代表根节点加上每个孩子节点子树中所有的节点。一共d个子树,节点数从N0到Nd-2,第i个孩子节点的最少节点数等于拥有的孙子节点=i-2,最后Nd=1+N0+N0+……+Nd-3+Nd-2,而类似的Nd-1=1+N0+N0+……+Nd-3,所以Nd=Nd-1+Nd-2,与斐波那契数列的递推公式一样,所以这个奇怪的数据结构就叫斐波那契堆了。在这里插入图片描述

思想

Sometimes it pays to let mess build up
把一些杂七杂八的事情堆起来,等一会有空了一起做完。

Your parents want lots of grandchildren
如果你自己丢了孩子,小心你爸妈不要你了。

Relaxed Heap

在我博客里找

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值