斐波那契堆,和二项堆类似,也是由一组最小堆有序的树构成。注意区别,不是二项树,是有根而无序的树。导论中,斐波那契堆只是具有理论上的意义,是以平摊分析为指导思想来设计的数据结构,主要是渐进时间界比二项堆有改善。斐波那契堆除去删除元素操作外,其他操作只有O(1)的平摊运行时间,而二项堆需要O(lgn)的最坏情况运行时间。但若要斐波那契堆能转化为实际应用,除要保证有相同平摊时间界限外,还需更简单的数据结构。导论中提到斐波那契堆应用于最小生成树和寻找单源最短路径,可重点理解。
总结来说,斐波那契堆,相对二项堆来说,在多数操作上改善渐进时间,但仅适用于删除操作动作较少的场景,且其结构复杂实用价值不大。下面看看斐波那契堆的结构及各操作的平摊分析势能方法。
斐波那契堆的结点,包含指向父结点的指针p[x]、指向任一子女的指针child[x]、左兄弟指针left[x]、右兄弟指针right[x]、结点子女个数degree[x]、标记是否失去子女mark[x]。值得一提的是,通过左右兄弟指针,子女结点被链接成一个环形双链表。
对给定的斐波那契堆H,通过指向包含最小关键字的树根的指向min[H]来访问,这个结点就是斐波那契堆中的最小结点。所有的树根通过left和right指针构成一个环形双链表,为堆的根表。指针min[H]指向根表中具有最小关键字的结点。H中所包含的结点个数为n[H]。斐波那契堆用势能方法分析其操作的平摊性能,对给定的斐波那契堆H,t(H)表示H根表中树的棵树,m(H)表示H中有标记结点的个数,势定义如下:
斐波那契堆的操作要分两类,一类是建堆、插入结点、合并堆和抽取堆最小值;另一类是关键字值减少和结点删除。
1)第一类操作斐波那契堆是一组无序的二项树,符合二项树的性质,性质四有所不同,描述为:无序二项树Uk,根的度数是k,大于任何其他结点的度数;根的子女按照某种顺序分别为U0,U1,…,Uk-1的根。n个结点的斐波那契堆由一组无序的二项树构成。基本操作的平摊性能都是O(1),抽取最小值是O(lgn)。
2)第二类操作斐波那契堆是一组无序的树,即不保持二项树性质,操作的平摊性能是O(lgn)。
建堆、插入结点(直接作为一颗二项树放在斐波那契堆的根表,然后将最小关键字指针重新调整)、寻找最小结点(第一个结点就是)、合并两个斐波那契堆(简单将两个堆的根表合并即可),都是O(1)代价。重点要说明抽取最小结点的算法,和第二类中关键字值减小和删除结点操作,都是O(lgn)代价。
1)抽取最小结点
抽取最小结点的处理逻辑很直接:将最小结点的子女都成为一个根,然后删除最小结点,接着将度数相同的根链接起来,调整到每个度数只有一个根为止。具体算法如下:
Fun_Fib_Heap_Extract_Min(H){
z=min[H];
if z ≠ null
then for each child x of z
do add x to root list of H
p[x]=null
remove z from the root list of H //将最小结点的子女调整为根并删除最小结点
if z=right[z] then min[H]=null
else min[H] = right[z] //将右兄弟作为最小结点,就是堆的入口结点
consolidate(H) //调整堆
n[H]=n[H]-1 //堆结点数减1
returun z
}
重点是堆调整,使根结点的度数都是唯一的,且保持无序二项树性质。
Fun_ consolidate(H){
for i=0 to D(n[H]) //结点度数上界
do A[i]=null
for each node w in the root list of H
do x=w
d=degree[x]
while A[d] ≠ null
do y=A[d]
if key[x] remove y form the root list of H make y a child of x,incrementingdegree[x] mark[y]=false A[d]=null d=d+1 A[d]=x //对根表中的每一个根w进行处理,使每个度数下至多只有一个根(发现有相同度数,小值作为根,大值下沉为子女),且数组A指向每一个留下的根。用A数组作为辅助数组,将根表中每个结点的度数映射过去。要对y进行标记,是势函数计算依据。这种处理思路就使用利用结点度数0-n的最大值作为数组来存储根表中结点的度数,是一种很有用的常量映射办法。 min[H]=null for i=0 to D(n[H]) do if A[i] ≠ null then add A[i] to the root list of H if min[H]=null orkey[A[i]] then min[H]=A[i] } 2)关键字值减小 斐波那契堆结点关键字值减小,主要是保持最小堆有序的性质,具体算法如下: Fun_Fib_Heap_DecreaseKey(H,x,k){//x结点关键值减少为k if k>key[x] then return; key[x]=k y=p[x] if y ≠ null and key[x] then CUT(H,x,y) Cascading-cut(H,y) If key[x] } 如果x结点减小后的关键值小于父结点y,则要调整堆,具体算法如下: Fun_CUT(H,x,y){ remove x from the child list ofy,decrementing degree[y] add x to the root list of H p[x] =null mark[x]=false } 将x链接到根表。 Fun_ Cascading-cut(H,y){ z=p[y] if z ≠null then if mark[y]=false //失去一个子结点 then mark[y]=true else CUT(H,y,z) Cascading-cut(H,z) } 级联处理,保持最小堆性质。删除结点就可以通过减少结点关键值和抽取最小结点来完成,先把要删除的结点关键值减少为负无穷大,然后抽取最小值结点来作为堆入口。