斐波那契堆(Fibonacci heaps)

article/2025/11/7 0:29:38

一:斐波那契堆

1:特性

斐波那契堆同二项堆一样,也是一种可合并堆。斐波那契堆的优势是:不涉及删除元素的操作仅需要O(1)的平摊运行时间(关于平摊分析的知识建议看《算法导论》第17章)。和二项堆一样,斐波那契堆由一组树构成。这种堆松散地基于二项堆,说松散是因为:如果不对斐波那契堆做任何DECREASE-KEY 或 DELETE 操作,则堆中每棵树就和二项树一样;但是如果执行这两种操作,在一些状态下必须要破坏二项树的特征,比如DECREASE-KEY或DELETE 后,有的树高为k,但是结点个数却少于2k。这种情况下,堆中的树不是二项树。
     与二项堆相比,斐波那契堆同样是由一组最小堆有序树构成。最小堆性质:每个结点的关键字大于等于父节点的关键字。但是斐波那契堆中的树都是有根而无序的,也就是说,单独的树满足最小堆特性,但是堆内树与树之间是无序的,如下图。
     对于斐波那契堆上的各种可合并操作,关键思想是可能久地将工作推后。例如,当向斐波那契堆中插入新结点、删除结点或合并两个斐波那契堆时,并不去合并树,而是将这个工作留给EXTRACT-MIN操作。


看一下斐波那契堆的链表结构表示:


2:每个结点的域:

//斐波那契结点ADT
struct FibonacciHeapNode {int key;       //结点int degree;    //度FibonacciHeapNode * left;  //左兄弟FibonacciHeapNode * right; //右兄弟FibonacciHeapNode * parent; //父结点FibonacciHeapNode * child;  //第一个孩子结点bool marked;           //是否被删除第1个孩子
};
typedef FibonacciHeapNode FibNode;


除了关键字key以外的结点信息:
1) 父节点*p
2) 指向任一子女的指针*child——结点x的子女被链接成一个环形双链表,称为x的子女表
3) 左兄弟*left
4) 右兄弟*right——当left[x] = right[x] = x时,说明x是独子。
5) 子女的个数degree[x]
6) 布尔值域mark[x]——标记是否失去了一个孩子


3:堆结构

对于一个给定的斐波那契堆H,可以通过指向包含最小关键字的树根的指针H.min来访问,这个结点被称为斐波那契堆中的最小结点。如果一个斐波那契堆H是空的,则H.min = NIL. 在一个斐波那契堆中,所有树的根都通过left和right指针链接成一个环形的双链表,称为堆的根表。于是,指针H.min就指向根表中具有最小关键字的结点。

指向根结点的堆结构体:

//斐波那契堆ADT
struct FibonacciHeap {int keyNum;   //堆中结点个数FibonacciHeapNode * min;//最小堆,根结点int maxNumOfDegree;   //最大度FibonacciHeapNode * * cons;//指向最大度的内存区域
};typedef FibonacciHeap FibHeap;


二:操作的实现

1:创建一个斐波那契堆

创建一个空的斐波那契堆,过程MAKE-FIB-HEAP 分配并返回一个斐波那契堆对象H;

//初始化一个空的Fibonacci Heap,堆结点信息
FibHeap * FibHeapMake()
{FibHeap * heap = NULL;heap = (FibHeap *) malloc(sizeof(FibHeap));if (NULL == heap) {puts("Out of Space!!");exit(1);}memset(heap, 0, sizeof(FibHeap));return heap;
}//初始化结点x
FibNode * FibHeapNodeMake()
{FibNode * x = NULL;x = (FibNode *) malloc(sizeof(FibNode));if (NULL == x) {puts("Out of Space!!");exit(1);}memset(x, 0, sizeof(FibNode));x->left = x->right = x;return x;
}

2:插入一个结点(仅将x结点插入到根链表中,不进行合并处理)

//函数调用前,结点x已经分配了空间,key已经被赋值
FIB-HEAP-INSERT(H, x)degree[x] ← 0    //初始化结点x的基本信息p[x] ← NILchild[x] ← NILleft[x] ← xright[x] ← xmark[x] ← FALSEif min[H] == NIL      //处理空树的情况creat a root list for H containing just xH.min = xelse  insert x into H's root listif key[x] < key[min[H]]then min[H] ← xn[H] ← n[H] + 1

如图是将关键字为21的结点插入斐波那契堆。该结点自成一棵最小堆有序树,从而被加入到根表中,成为根的左兄弟。



3:合并两个斐波那契堆

仅仅简单地将H1和H2的两根表并置,然后确定一个新的最小结点。

伪代码:

FIB-HEAP-UNION(H1, H2)
{H ← MAKE-FIB-HEAP() //创建一个空的堆,用来存放H1,H2min[H] ← min[H1]concatenate the root list of H2 with the root list of Hif (min[H1] = NIL) or (min[H2] ≠ NIL and min[H2] < min[H1]) //找到关键字最小的结点then min[H] ← min[H2]n[H] ← n[H1] + n[H2]    //堆元素的总个数free the objects H1 and H2  //这里h1、h2是指向根结点的堆结构体return H
}

最后要销毁H1、H2,并不是将原来的两个堆全销毁,只是销毁的是头结点信息。由于已经创建了H 结点,合并后的堆的信息存储在H中。


4:抽取最小结点

前边说过,对根表中的树合并是推后到EXTRACT-MIN中的,所以抽取最小这个操作比较麻烦。该过程还用到一个辅助过程CONSOLIDATE。

先上图,对整个变换过程有个了解




下面说一下EXTRACT-MIN过程的实现(上图中的a->b过程)

//取出最小值后,调整堆,并且满足根链表结点的度不相同
FIB-HEAP-EXTRACT-MIN(H)
{z ← min[H]      if z ≠ NIL  //如果z非空,则取出了最小值for each child x of z   //将z结点的孩子全部放入根链表当中add x to the root list of Hp[x] ← NIL  //同时调整刚加入根链表结点的父指针信息remove z from the root list of H    //将孩子结点信息加入根链表以后,在新的链表中删除z,这时z已经没有孩子结点if z == right[z]     //如果二者相等,证明当前状态下,整个堆中只有z一个结点min[H] ← NILelse min[H] ← right[z]  //如果z不是唯一结点,则随便将根链表中的结点当成临时的最小值CONSOLIDATE(H)      //调整堆结构,并求得最小值n[H] ← n[H] – 1 //调整信息return z
}
评注:这里只做了三件事:将z结点的孩子结点放入根链表,移除z结点(只是移除,z结点的指针依然不变化),h.min指向根链表某一个结点。

主要任务还是要在CONSOLIDATE()中来实现

CONSOLIDATE过程要做的工作是:使每个度数的二项树唯一,也就是使每个根都有一个不同的degree值为止。对根表的合并过程是反复执行下面的步骤:
1)在根表中找出两个具有相同度数的根x和y,且key[x] <= key[y].(如果不满足,则交换,保持key[x]<=key[y])
2)将y链接到x:将y从根表中移出,成为x的一个孩子。这个过程由FIB-HEAP-LINK完成。

这个过程中使用一个辅助数组指针A[0...D(H.n)]来记录根结点对应的度数的信息,指向度数为i的结点。比如,当遍历到某一结点x时,这个结点的度数为d,那么判断A[d]是否为空,不为空,证明具有相同的度数d,将当前结点x和A[d]指向的结点y合并;然后将x度数递增1,然后递归进行与A[d+1]的判断。

CONSOLIDATE(H)
1 for i ← 0 to D(n[H])  //初始化数组A[]
2      do A[i] ← NIL
3 for each node w in the root list of H
4      do x ← w
5         d ← degree[x]
6         while A[d] ≠ NIL
7             do y ← A[d]     //Another node with the same degree as x.
8                if key[x] > key[y] //保持key[x] <= key[y]
9                   then exchange x ↔ y
10                FIB-HEAP-LINK(H, y, x)    //将y变为x的子节点
11                A[d] ← NIL    //必须更新A[d]
12                d ← d + 1
13         A[d] ← x
14 min[H] ← NIL
15 for i ← 0 to D(n[H])     //遍历整个数组,将根结点插入到根链表当中。这里重新创建根链表
16      do if A[i] == NIL
17            then create a root list for H containing just A[i]
18                 min[H] ← A[i]
19         else insert A[i] into H's root list
20               if key[A[i]] < key[min[H]]
21                    then min[H] ← A[i]FIB-HEAP-LINK(H, y, x)
1  remove y from the root list of H
2  make y a child of x, incrementing degree[x]
3  mark[y] ← FALSE

5:关键字减小

减小关键字操作最大的难点是,如果减小后的结点破坏了最小堆的性质,如何维护斐波那契堆的性质。这里用到一个操作:级联剪枝(Cascading Cut)。减小关键字的代码流程基本就是:如果减小后的结点破坏了最小堆性质,则把它切下来(cut),即从所在双向链表中删除,并将其插入到由最小树根节点形成的双向链表中,然后再从parent[x]到所在树根节点递归执行级联剪枝。

关于级联剪枝,《数据结构》中的解释:
由于增加了删除和关键字减值操作,所以,F堆中的最小树就不一定必须是二项树了。事实上,可能存在度为k却只有k + 1(原书是k + 1,应该是k – 1吧)个结点的最小树。为了保证每个度为k的最小树至少包含ck个结点(c > 1), 每次执行删除操作和关键字减值操作后,还必须进行级联剪枝操作。为此,为每个结点增加一个布尔类型的child_cut域(即本文里的marked)。child_cut域的值仅对那些不是最小树树根的结点有意义。对于不是最小树树根的结点x, x的child_cut域为TRUE,当且仅当在最近一次x成为其当前父结点的儿子之后,x的一个儿子被删除。这就意味着,在执行删除最小元素中,每次连接两棵最小树时,关键字值较大的根结点的child_cut域应该赋值为FALSE。更进一步地说,一旦删除操作或关键字减值操作将最小树的非根结点q从其所在双向链表中删除时,则调用级联剪枝操作。在执行级联剪枝操作过程中,检查从被删除结点q的父节点p开始,到被删节点的最近的child_cut域为FALSE的祖先结点的路径。对在该路径上所有child_cut域为TRUE的非根结点,将其从所在的双向链表中删除,并将其加入到F堆的最小树的根节点组成的双向链表中。如果该路径上存在child_cut域为FALSE的结点 ,则将其该域的值修改为TRUE。


伪代码

FIB-HEAP-DECREASE-KEY(H, x, k)
1  if k > key[x]    //保证值是减小的
2     then error "new key is greater than current key"
3  key[x] ← k
4  y ← p[x] 
5  if y ≠ NIL and key[x] < key[y]   //与父节点相比较,如果比父节点小,进入循环
6     then CUT(H, x, y)             //将x从y中移除,并放到根链表中,当然,x的孩子也要随着x一起走,
7          CASCADING-CUT(H, y)      //级联剪切判断,判断非根结点y是否失去的是第二个孩子。
8  if key[x] < key[min[H]]
9      then min[H] ← xCUT(H, x, y)
1 remove x from the child list of y, decrementing degree[y]
2 add x to the root list of H
3 p[x] ← NIL
4 mark[x] ← FALSE//当y不是根结点时,失去第一个孩子时,标记为true,失去第二个孩子时,将y移除放入根链表,递归处理y的父节点。
CASCADING-CUT(H, y)
1 z ← p[y]
2 if z ≠ NIL
3    then if mark[y] = FALSE
4            then mark[y] ← TRUE
5            else CUT(H, y, z)
6                 CASCADING-CUT(H, z)

图形实例:



级联剪切的过程很明了,我当时看的时候最烦的问题是,为什么要进行级联剪切,级联剪切丫的要干什么? 
     如果仅仅要切除父结点y的一个结点x,则仅仅需要把结点x加入到根结点所在双向链表中,再检测y是否marked == true即可,这是因为斐波那契中的树并不一定是二项树,近似二项树也可以。当删除y的第二个结点时,对在该路径上所有marked域为TRUE的非根结点,将其从所在的双向链表中删除,并将其加入到F堆的最小树的根节点组成的双向链表中,即只有在删除同一个结点偶数个孩子时,才要进行级联剪枝,来维护二项树性质,奇数个时(即一个),对树影响不大,莫管它,只标记一下即可。
为什么偶数个的时候要递归往上删除?
     二项树中在深度为i处恰有Cik个结点(I = 0, 1, 2, ……, k)。试着如果不进行级联剪枝,就可以发现,稍微删得结点超过两三个,最后的树就会不成样子,毫无章法。但是如果进行了级联剪枝,在偶数个结点时进行级联剪切时,原来是C30 = 1, C31 = 3, C32 = 3, C33 = 1, 减少两个结点关键字后,变为:C20 = 0,C21 = 2, C22 = 1;二项式是对称的,所以,偶数个结点时进行级联剪枝可以保证类似上边的正好使二项式减少一个数量级。


八、删除一个结点
伪代码:
FIB-HEAP-DELETE(H, x)
1 FIB-HEAP-DECREASE-KEY(H, x, -∞)
2 FIB-HEAP-EXTRACT-MIN(H)
过程很简单,先减小直到min[H], 然后直接剔除最小值即可


详细代码:斐波那契堆实现文件C语言




参考:斐波那契堆(Fibonacci heaps)



http://chatgpt.dhexx.cn/article/fgJ7rUmJ.shtml

相关文章

3.3 斐波那契堆

结构 斐波那契堆的基础是可合并堆。 数据结构是一个森林。也就是N棵树。这点和二项堆一样。 这个结构没有二项堆那么多的要求。 Rank的概念,是子节点的数目 与二项堆不同的是&#xff0c;斐波那契堆的底层链表要成环&#xff0c;要双向链表。   而斐波那契堆的节点&#xff…

二叉堆/二项堆/斐波那契堆

二叉堆 二叉树 二叉树&#xff1a;是树的一种&#xff0c;主要的特点是二叉树的所有节点最多只有两个叶节点。除此之外没有别的要求完全二叉树&#xff1a;就是在二叉树当中&#xff0c;除了最后一层之外&#xff0c;所有层的节点都有满的&#xff0c;且最后一层的节点也是从…

斐波那契堆(Fibonacci heap)原理详解

前言 斐波那契堆(Fibonacci heap)是计算机科学中最小堆有序树的集合。它和二项式堆有类似的性质&#xff0c;但比二项式堆有更好的均摊时间。堆的名字来源于斐波那契数&#xff0c;它常用于分析运行时间。 堆结构介绍 基本术语介绍&#xff1a; 关键字&#xff1a;堆节点储存的…

斐波那契堆 - 解析与实现

概要 本章介绍斐波那契堆。和以往一样&#xff0c;本文会先对斐波那契堆的理论知识进行简单介绍&#xff0c;然后给出C语言的实现。后续再分别给出C和Java版本的实现&#xff1b;实现的语言虽不同&#xff0c;但是原理如出一辙&#xff0c;选择其中之一进行了解即可。若文章有…

算法导论 斐波那契堆

算法导论 斐波那契堆 定义 堆H 最小结点min&#xff1a;指向最小关键字key的根结点n表示当前堆中结点的个数 结点x 最小堆性质&#xff1a;每个结点的关键字key均大于等于父结点的关键字根链表&#xff1a;所有的根结点都通过left,right指针形成一个环形链表父类指针为p,左右兄…

斐波那契堆(Fibonacci heap)原理详解(附java代码实现)

前言 斐波那契堆(Fibonacci heap)是计算机科学中最小堆有序树的集合。它和二项式堆有类似的性质&#xff0c;但比二项式堆有更好的均摊时间。堆的名字来源于斐波那契数&#xff0c;它常用于分析运行时间。 堆结构介绍 基本术语介绍&#xff1a; 关键字&#xff1a;堆节点储存的…

斐波那契堆

斐波那契堆 作者&#xff1a; 大树先生 博客&#xff1a; http://blog.csdn.net/koala_tree GitHub&#xff1a;https://github.com/koalatree 2017 年 09 月 13 日 自《算法导论》. 斐波那契堆有两种用途&#xff1a;第一种&#xff0c;支持一系列操作&#xff0c;这些操作…

斐波那契堆(Fibonacci Heap)

也许我们每个人都知道斐波那契数列&#xff08;Fibonacci sequence&#xff09;。即这样一个数列&#xff1a;1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144...,如果我们用伪代码比表示: int FibonacciSequence(int n){if (n 1 || n 2) {return 1;}return FibonacciSequence(n …

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

出自&#xff1a;http://www.cnblogs.com/skywang12345/p/3659060.html 斐波那契堆(一)之 图文解析 和 C语言的实现 概要 本章介绍斐波那契堆。和以往一样&#xff0c;本文会先对斐波那契堆的理论知识进行简单介绍&#xff0c;然后给出C语言的实现。后续再分别给出C和Java版本…

斐波那契堆的C++实现

本文改编自《算法导论》第三版第19章&#xff1a;斐波那契堆。文中代码为书中伪代码的C实现&#xff0c;如有疏漏还请指出。 1.斐波那契堆的结构 斐波那契堆是一种可并堆&#xff0c;它支持以下操作&#xff1a; ( 1 ) (1) (1)在 O ( 1 ) O(1) O(1)时间内插入元素、获取最小…

常见电子元器件检测方法。——Arvin

电子设备中使用着大量各种类型的电子元器件&#xff0c;设备发生故障大多是由于电子元器件失效或损坏引起的。因此怎么正确检测电子元器件就显得尤其重要&#xff0c;这也是电子维修人员必须掌握的技能。我在电器维修中积累了部分常见电子元器件检测经验和技巧&#xff0c;供大…

电工电子复习题

1、应用叠加定理时&#xff0c;理想电压源不作用时视为______&#xff0c; 理想电流源不作用时视为____。 2、电容元件具有“通_____隔_____”的特性&#xff0c; 电感元件具有通___阳___ ”的特性&#xff0c;。 3、已知:“u 60sin(628t -135度)(v),则其有效值为____V,角频率为…

我的元器件入门

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 元器件 前言一、电阻器1、什么是电阻器2、电阻的标示方法和测量方法3、电阻的特性4、基本参数5、电阻的功能 二、电容器1、什么是电容&#xff1f;组成&#xff0c;基本参数&…

2023年天津中德应用技术大学专升本机械电子工程专业考试大纲

天津中德应用技术大学 机械电子工程专业&#xff08;高职升本科&#xff09; 2023年专业基础考试大纲 备注&#xff1a;考试题型&#xff1a;填空题、选择题、判断题、问答题、分析题、设计与计算题 《机械设计基础》主要知识点 第一部分常用机构 一、平面机构运动分析 知…

2021中级维修电工证考试题库2021职业技能鉴定

题库来源:特种作业模考题库小程序 1.三相负载作Y接时,无论负载对称与否,线电流总等于相电流。 √ 2.双向晶闸管是( )半导体结构。 B A.四层 B.五层 C.三层 D.二层 3.符合有“0”得“0”,全“1”得“1”的逻辑关系的逻辑门是( )。 B A.或门 B.与门 C.非门 D.或非门 4.示波…

2018第八届至2022年第十三届蓝桥杯单片机开放与设计省赛客观题及简解整理

前言&#xff1a; 由于本人马上要参加第十四届蓝桥杯单片机设计与开发的省赛了&#xff0c;在对客观题复习两轮后&#xff0c;发现效率是比较低的&#xff0c;因此整理了2018至2022年的省赛客观题&#xff0c;将大概的考点划分三部分&#xff0c;这样可以更加系统的复习其内容。…

晶体管频率特性——高频等效模型、频率特性、π模型的单向化

晶体管高频等效模型 通过之前的定性分析得出在高频情况下晶体管结电容将对信号传输带来较大影响。之前的 h 参数等效模型没有考虑结电容的影响&#xff0c;因此不再适用&#xff0c;此时要用新的模型来反映晶体管的结电容&#xff0c;这就是高频等效模型。 此时从晶体管的实际…

[渝粤教育] 郑州航空工业管理学院 电工电子技术基础 参考 资料

教育 -电工电子技术基础-章节资料考试资料-郑州航空工业管理学院【】 小节测试 1、【判断题】任何一个完整的电路都必须有电源、负载和中间环节三个基本部分组成。 A、正确 B、错误 参考资料【 】 2、【判断题】电路的作用是对电能进行传输、分配和转换&#xff1b;而对电信号进…

服务于期末考试的计算机硬件基础资料

电路的基本概念和基本定律 电流和电路模型 理想元件、理想电路、集总参数元件、集总参数电路 集总元件&#xff1a; 当电路器件的尺寸远小于电路最高工作频率所对应的波长时&#xff0c;可以认为元件的参数“集总”于一个点上&#xff0c;形成所谓的集总参数元件。 理想元件&am…

您需要了解的跨阻放大器——第1部分

跨阻放大器(TIA)是光学传感器(如光电二极管)的前端放大器,用于将传感器的输出电流转换为电压。跨阻放大器的概念很简单,即运算放大器(op amp)两端的反馈电阻(RF)使用欧姆定律VOUT= I RF 将电流(I)转换为电压(VOUT)。在这一系列博文中,我将介绍如何补偿TIA,及如…