【问题标题】:Has anyone actually implemented a Fibonacci-Heap efficiently?有没有人真正有效地实现了斐波那契堆?
【发布时间】:2026-01-06 03:20:16
【问题描述】:

你们有没有人实现过Fibonacci-Heap?几年前我就这样做了,但它比使用基于数组的 BinHeaps 慢了几个数量级。

当时,我认为这是一个宝贵的教训,说明研究并不总是像它声称的那样好。然而,许多研究论文声称他们的算法的运行时间基于使用斐波那契堆。

您是否曾经设法产生有效的实施?或者您是否使用过大到斐波那契堆效率更高的数据集?如果是这样,一些细节将不胜感激。

【问题讨论】:

  • 你没学过这些算法老哥们总是把他们的大常数隐藏在他们的大字后面哦?! :) 在实践中,大多数时候,“n”的东西似乎永远不会接近“n0”!
  • 我知道了——现在。当我第一次拿到“算法简介”的副本时,我就实现了它。另外,我没有为会发明无用数据结构的人选择 Tarjan,因为他的 Splay-Trees 实际上很酷。
  • mdm:当然不是没用,但就像插入排序在小数据集中优于快速排序一样,二叉堆可能会因为更小的常量而更好地工作。
  • 实际上,我需要堆的程序是在 VLSI 芯片中寻找 Steiner-Trees 进行路由,因此数据集并不小。但现在(除了排序等简单的东西)我总是使用更简单的算法,直到它在数据集上“中断”。
  • 我对此的回答实际上是“是”。 (嗯,我在一篇论文上的合著者做到了。)我现在没有代码,所以在我真正回复之前我会得到更多信息。然而,查看我们的图表,我注意到 F 堆进行的比较少于 b 堆。您是否使用了比较便宜的东西?

标签: performance algorithm language-agnostic data-structures fibonacci-heap


【解决方案1】:

Boost C++ librariesboost/pending/fibonacci_heap.hpp 中包含斐波那契堆的实现。这个文件显然已经在pending/ 中多年了,根据我的预测将永远不会被接受。此外,该实现中存在错误,这些错误已由我的熟人和全能酷家伙 Aaron Windsor 修复。不幸的是,我可以在网上找到的该文件的大多数版本(以及 Ubuntu 的 libboost-dev 包中的那个)仍然存在错误。我不得不从 Subversion 存储库中提取 a clean version

自从版本1.49Boost C++ libraries 增加了很多新的堆结构,包括斐波那契堆。

我能够将dijkstra_heap_performance.cppdijkstra_shortest_paths.hpp 的修改版本进行编译,以比较斐波那契堆和二进制堆。 (在typedef relaxed_heap<Vertex, IndirectCmp, IndexMap> MutableQueue 行中,将relaxed 更改为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.

据我了解,这涉及到斐波那契堆和二叉堆之间的根本区别。两种数据结构之间唯一真正的理论区别是斐波那契堆在(摊销的)恒定时间内支持递减键。另一方面,二叉堆从它们作为数组的实现中获得了很大的性能。使用显式指针结构意味着斐波那契堆会遭受巨大的性能损失。

因此,在实践中,要从斐波那契堆中受益,您必须在 reduce_key 非常频繁的应用程序中使用它们。就 Dijkstra 而言,这意味着底层图是密集的。一些应用程序可能本质上是减少键强度;我想尝试the Nagomochi-Ibaraki minimum-cut algorithm,因为它显然会生成很多 reduce_keys,但要让时序比较工作起来太费劲了。

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

理论说明:Fibonacci heaps 在 reduction_key 上的改进性能对于理论应用很重要,例如 Dijkstra 的运行时。斐波那契堆在插入和合并方面也优于二叉堆(斐波那契堆的摊销常数时间)。插入本质上是无关紧要的,因为它不会影响 Dijkstra 的运行时,并且修改二进制堆以在摊销的常数时间内也有插入是相当容易的。在恒定时间内合并很棒,但与此应用程序无关。

个人笔记:我和一个朋友曾经写过一篇论文,解释了一个新的优先级队列,它试图在不复杂的情况下复制斐波那契堆的(理论)运行时间。这篇论文从未发表过,但我的合著者确实实现了二进制堆、斐波那契堆和我们自己的优先级队列来比较数据结构。实验结果的图表表明,斐波那契堆在总比较方面略胜二叉堆,这表明斐波那契堆在比较成本超过开销的情况下表现更好。不幸的是,我没有可用的代码,而且在你的情况下,比较可能很便宜,所以这些 cmets 是相关的,但不直接适用。

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

【讨论】:

  • 谢谢!这个问题在我脑海中盘踞了很久。在我尝试 Steiner-Trees 之前,我实际上使用 Fibonacci-Heaps 实现了 Dijkstra's。但是,我的图表似乎比您的示例中的密度小得多。他们有数百万个节点,但平均度数只有 5-6。
  • Fib Heap 的性能可以通过操作顺序来预测。我编写了一个堆重算法,最终使用 Fib 堆(与 Bin 堆)更快。诀窍是批量处理工作。无论任何操作的频率如何,区别都在这里:DecreaseKey - ExtractMin - DecreaseKey - ExtractMin 与 DecreaseKey - DecreaseKey - ExtractMin - ExtractMin(下文续)
  • 后者大约快两倍,因为第二个 ExtractMin 几乎是免费的。我们的算法提取了一批 Min 元素,其中许多被丢弃;在 Bin Heap 上浪费,但在 Fib Heap 上更好。遗憾的是,人们在谈论基于堆的算法时提供的时间复杂度并没有清楚地反映出这一点。使用 Amortized bounds,总复杂度不仅仅是 # 个操作 * 操作的复杂性。
  • 还有机会尝试配对堆和/或松弛堆吗?
  • 我不知道为什么你的结果看起来如此接近,我使用了 STL priority_queue 与自我实现的斐波那契堆,而二进制堆在我的tests 中远远落后。
【解决方案2】:

Knuth 在 1993 年为他的书 Stanford Graphbase 比较了斐波那契堆和二叉堆的最小生成树。他发现斐波那契在他正在测试的图形大小下比二叉堆慢 30% 到 60%,不同密度的 128 个顶点。

source code 位于 MILES_SPAN 部分中的 C(或者更确切地说是 CWEB,它是 C、数学和 TeX 的交叉)中。

【讨论】:

    【解决方案3】:

    免责声明

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


    也许我做错了什么,但我wrote a test,基于A.Rex anwser 比较:

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

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

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


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

    【讨论】:

    • 每种情况下有多少边?你到底在运行什么算法?如果我们不知道我们在处理什么,你的结果就没有意义。
    • 我很伤心,所有的图都是完整的,所以你可以计算每个案例的边数。您的意思是,“您是否正在运行”。他们在桌子的前面。
    • 这看起来运行时间完全由堆以外的东西支配(它可能是生成图形或一些 IO)。这些几乎完全相同的结果令人难以置信。
    • 好吧,也许时间操作系统被其他东西支配,但我确信那不是 IO 或图形的生成。无论如何,源代码是可用的,如果有人发现错误并纠正错误,我将非常高兴。
    • 这些测试似乎测量的是完全不同的东西。你能评论你运行的测试吗?请记住,如果距离是几何/欧几里得,则完整图上的最短路径问题是 O(1)。
    【解决方案4】:

    我还用斐波那契堆做了一个小实验。这是详细信息的链接:Experimenting-with-dijkstras-algorithm。我刚刚搜索了术语“Fibonacci heap java”,并尝试了一些现有的 Fibonacci heap 的开源实现。似乎其中一些有一些性能问题,但也有一些相当不错。至少,它们在我的测试中击败了幼稚的二进制堆 PQ 性能。也许他们可以帮助实现高效。

    【讨论】:

      最近更新 更多