有人实际上有效地实施了斐波那契堆吗?


151

你们中有没有人实施过斐波那契堆?几年前,我这样做了,但是比使用基于数组的BinHeaps要慢几个数量级。

那时,我认为这是一门宝贵的课程,说明研究并不总是像它声称的那样好。但是,许多研究论文声称其算法基于使用斐波那契堆的运行时间。

您是否曾经设法产生有效的实施方案?还是您使用的数据集如此之大,以至于斐波那契堆更有效?如果是这样,一些细节将不胜感激。


25
您是否不知道这些算法家伙总是将它们的巨大常量隐藏在它们的大big后面?!:)实际上,在大多数情况下,似乎“ n”永远都不会接近“ n0”!
Mehrdad Afshari

我现在知道了。我第一次获得“算法简介”的副本时就实现了它。另外,我没有选择Tarjan来寻找会发明无用数据结构的人,因为他的Splay-Trees实际上很酷。
mdm

mdm:当然,它并不是没有用的,但是就像插入排序在小型数据集中击败了quicksort一样,由于常量较小,二进制堆可能会更好地工作。
Mehrdad Afshari,2009年

1
实际上,我需要堆的程序是在VLSI芯片中找到Steiner-Trees进行路由,因此数据集并不是很小。但是如今(除了诸如排序之类的简单事物之外),我将始终使用更简单的算法,直到它在数据集上“中断”为止。
mdm

1
我对此的回答实际上是“是”。(好吧,我的论文的合著者做了。)我现在没有代码,因此在我真正回复之前,我将获得更多信息。但是,从我们的图表来看,我注意到F堆比b堆进行的比较少。您使用的是比较便宜的东西吗?
A. Rex

Answers:


136

所述升压C ++库包括斐波那契堆在一个实现boost/pending/fibonacci_heap.hpp。该文件显然已经存在pending/多年,根据我的预测,该文件将永远不会被接受。另外,该实现中也存在一些错误,这些错误是由我和熟人Aaron Windsor所熟识的。不幸的是,我可以在网上找到的那个文件的大多数版本(以及Ubuntu的libboost-dev软件包中的那个)仍然存在bug。我不得不从Subversion存储库中提取一个干净的版本

1.49 版开始,Boost C ++库添加了许多新的堆结构,包括斐波那契堆。

我能够编译dijkstra_heap_performance.cpp对修改后的版本dijkstra_shortest_paths.hpp比较斐波那契堆和二进制堆。(在一行中typedef relaxed_heap<Vertex, IndirectCmp, IndexMap> MutableQueue,更改relaxedfibonacci。)我首先忘记进行优化编译,在这种情况下,Fibonacci和二进制堆的性能大致相同,而Fibonacci堆的性能通常不高。经过非常强大的优化编译后,二进制堆得到了极大的提升。在我的测试中,当图非常大且密集时,斐波那契堆的性能仅优于二进制堆,例如:

Generating graph...10000 vertices, 20000000 edges.
Running Dijkstra's with binary heap...1.46 seconds.
Running Dijkstra's with Fibonacci heap...1.31 seconds.
Speedup = 1.1145.

据我了解,这涉及到斐波纳契堆和二进制堆之间的根本区别。两种数据结构之间唯一真正的理论差异是,斐波那契堆在(摊销后的)恒定时间内支持减少密钥。另一方面,二进制堆通过以数组的形式实现而获得了很多性能。使用显式指针结构意味着斐波那契堆遭受巨大的性能损失。

因此,要从实践中受益于Fibonacci堆,您必须在reduce_keys非常频繁的应用程序中使用它们。用Dijkstra来说,这意味着基础图是密集的。某些应用程序可能本质上是reduce_key-intense; 我想尝试Nagomochi-Ibaraki最小删节算法,因为它显然会生成很多reduce_keys,但是要使时序比较正常工作需要付出很大的努力。

警告:我可能做错了什么。您不妨尝试自己重现这些结果。

理论注释:Fibonacci堆在reduce_key上的性能提高对于理论应用程序(例如Dijkstra的运行时)很重要。Fibonacci堆在插入和合并时也胜过二进制堆(Fibonacci堆的均摊固定时间)。插入本质上是无关紧要的,因为它不会影响Dijkstra的运行时,并且修改二进制堆以在固定的固定时间内插入也相当容易。固定时间合并非常好,但与该应用程序无关。

个人说明:我的一个朋友和我曾经写过一篇论文,解释了一个新的优先级队列,该队列试图在没有复杂性的情况下复制Fibonacci堆的(理论上)运行时间。该论文从未发表过,但是我的合著者确实实现了二进制堆,斐波那契堆以及我们自己的优先级队列来比较数据结构。实验结果的图表表明,就总比较而言,斐波那契堆的性能稍好于二进制堆,这表明在比较成本超过开销的情况下,斐波那契堆的性能会更好。不幸的是,我没有可用的代码,大概在您的情况下比较便宜,因此这些注释是相关的,但不直接适用。

顺便提一句,我强烈建议尝试将Fibonacci堆的运行时与您自己的数据结构进行匹配。我发现自己只是重新发明了斐波那契堆。在我以为斐波那契堆的所有复杂性都是一些随机的想法之前,但后来我意识到它们都是自然的并且相当强迫。


谢谢!这个问题已经困扰了我很长时间。在尝试Steiner-Trees之前,我实际上是使用Fibonacci-Heaps实现Dijkstra的。但是,似乎我的图的密度比您的示例要小得多。他们有数百万个节点,但平均程度仅为5-6。
mdm,2009年

Fib Heap的性能可以通过一系列操作来预测。我编写了一个重堆算法,最终以Fib堆(与Bin Heap相比)更快。诀窍是分批处理工作。无论任何操作的频率如何,其区别都在于此:DecreaseKey-ExtractMin-DecreaseKey-ExtractMin与DecreaseKey-DecreaseKey
ExtractMin

后者的速度大约是以前的两倍,因为第二个ExtractMin几乎是免费的。我们的算法提取了一批Min元素,其中许多元素被丢弃了;在Bin堆上浪费,但在Fib堆上浪费。令人遗憾的是,这在人们谈论基于堆的算法时所提供的时间复杂性中并未明确体现。使用摊销范围,总的复杂度不只是#运算*运算复杂度。
Gaminic

1
也有可能尝试配对堆和/或宽松堆吗?
Thomas Ahle 2014年

1
我不确定为什么您的结果看起来如此接近,我使用了STL priority_queue与自我实现的fibonacci堆进行比较,而在我的测试中,二进制堆大大落后了。
Nicholas Pipitone

33

克努斯(1993年)在他的书“ 斯坦福Graphbase”中对斐波那契堆和二进制堆进行了最小生成树的比较。他发现在测试的图形尺寸下,斐波那契比二进制堆慢30%至60%,在不同密度下有128个顶点。

源代码是在C(或者更确切地说CWEB其是C,数学和TeX的之间的交叉)的部分MILES_SPAN。


1

免责声明

我知道结果非常相似,并且“看起来运行时间完全由堆以外的其他东西控制”(@Alpedar)。但是我在代码中找不到任何证据。它的代码是开放的,因此,如果您发现任何可能影响测试结果的内容,请告诉我。


也许我做错了什么,但是我基于A.Rex anwser比较编写了一个测试

  • 斐波那契堆
  • D-Ary-堆(4)
  • 二进制堆
  • 轻松堆

所有堆的执行时间(仅适用于完整图形)非常接近。对具有1000、2000、3000、4000、5000、6000、7000和8000个顶点的完整图形进行了测试。对于每个测试,生成了50个随机图,并且输出是每个堆的平均时间:

抱歉,输出不是很冗长,因为我需要它从文本文件中构建一些图表。


结果如下(以秒为单位):

堆结果表


4
每种情况下有多少条边?您到底在运行什么算法?如果我们不知道要处理的内容,那么您的结果就没有意义。
kokx

令人遗憾的是,所有图形都是完整的,因此您可以计算每种情况下的边数。你的意思是“你在精确地跑步吗”。它们在表格的开头。
Guilherme Torres Castro

22
看起来运行时间完全由堆以外的东西控制(它可能是生成图形或某些IO)。那些几乎完全相同的结果令人难以置信。
Alpedar

2
嗯,也许时间是由其他东西主导的,但是我敢肯定那不是IO或图表的生成。无论如何,源代码是可用的,如果有人发现错误并纠正问题,我将非常高兴。
Guilherme Torres Castro

这些测试似乎在衡量完全不同的东西。您能对您进行的测试发表评论吗?请记住,如果距离为几何/欧几里得,则完整图形上的最短路径问题为O(1)。
Gaminic

0

我还对斐波那契堆做了一个小实验。这是详细信息的链接:Experimenting-dijkstras-algorithm。我只是用谷歌搜索“斐波那契堆java”一词,并尝试了一些斐波那契堆的现有开源实现。似乎其中一些存在性能问题,但是有些还不错。至少,在我的测试中,它们击败了天真和二进制堆PQ性能。也许他们可以帮助实现高效的解决方案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.