就像被问到here一样, 我无法理解如何在堆中找到松弛顶点的索引。
风格编程,heap
是一个黑盒子,它抽象出优先级队列的细节。现在,如果我们需要维护一个将顶点键映射到堆数组中相应索引的哈希表,那么需要在heap
实现中完成,对吧?
但是大多数标准堆都不提供执行此类映射的哈希表。
处理这整个问题的另一种方法是将松弛的顶点添加到堆中,而不管是什么。当我们提取最小值时,我们将得到最好的一个。为防止多次提取相同的顶点,我们可以将其标记为已访问。
所以我的确切问题是,处理这个问题的典型的方式(在行业中)是什么?
与我提到的方法相比,有什么优点和缺点?
答案 0 :(得分:3)
通常,您需要一个特殊构造的优先级队列来支持decreaseKey
操作,以使其正常工作。我已经看到这是通过让优先级队列显式跟踪索引的哈希表(如果使用二进制堆),或者通过具有侵入性优先级队列来实现的,其中存储的元素是堆中的节点(如果使用二项式堆)或者斐波那契堆,例如)。有时,优先级队列的插入操作将返回指向保存新添加的密钥的优先级队列中的节点的指针。例如,此处是支持decreaseKey
的{{3}}。它的工作原理是让每个插入操作返回一个指向Fibonacci堆中节点的指针,这样就可以在O(1)中查找节点,假设你跟踪返回的指针。
希望这有帮助!
答案 1 :(得分:1)
编程风格,堆是一个黑盒子,抽象出优先级队列的细节。
不一定。 C++和Python都有堆库,它们在数组上提供函数而不是黑盒子对象。 Go抽象了一点,但要求程序员为其堆操作提供类似数组的数据结构。
所有这些在标准化,行业强度库中泄漏的抽象都有一个原因:某些算法(Dijkstra)需要一个带有附加操作的堆,这会降低其他算法的性能。然而,其他算法(heapsort)需要在输入数组上就地工作的堆操作。如果你的库的堆给你一个黑盒子对象,并且它不适合某些算法,那么是时候将这些操作重新实现为数组上的函数,或者找到一个具有你需要的操作的库。
答案 2 :(得分:1)
您正在询问一些非常有效的问题,但遗憾的是它们有点模糊,因此我们无法为您提供100%可靠的“行业标准”答案。但是,无论如何,我会试着回过头来看看:
编程风格,堆是一个黑盒子,它抽象出优先级队列的细节
从技术上讲,优先级队列是抽象接口(插入具有优先级的元素,提取最低优先级元素),堆是具体实现(基于数组的堆,二项式堆,斐波纳契堆等)。
我想说的是,使用数组只是实现优先级队列的一种特殊方式。
现在,如果我们需要维护一个将顶点键映射到堆数组中相应索引的哈希表,那么需要在堆实现中完成,对吧?
是的,因为每次在数组中移动元素时,都需要更新哈希表中的索引。
但是大多数标准堆都不提供执行此类映射的哈希表。
是。这可能非常烦人。
处理这整个问题的另一种方法是将松弛的顶点添加到堆中,无论如何。
我想这可行,但我不认为我见过有人那样做。在这里使用堆的重点是提高性能,并通过向堆添加冗余元素来反对它。当然,你保留优先级队列的“黑盒子”,但我不知道这是否值得。此外,额外的pop_heap操作可能会对您的渐近复杂性产生负面影响,但我必须进行数学检查。
处理这个问题的典型方式(业内)是什么?
首先,问问自己是否可以使用哑数组而不是优先级队列。 当然,找到现在的O(N)而不是O(log n)中的最小元素,但实现是最简单的(它本身就是一个优点)。此外,如果图形密集,使用数组也会同样有效,即使图形稀疏也可能有效,具体取决于图形的大小。
如果您确实需要优先级队列,那么您将不得不找到一个实现了reduceKey操作的队列。如果你找不到一个,我会说它自己实现它并不是那么糟糕 - 它可能比试图找到一个现有的实现然后尝试将其与你的其余代码相匹配更少麻烦。
最后,我不建议使用真正花哨的堆数据结构(例如fibonacci堆)。虽然这些经常出现在教科书中作为获得最优渐近渐正的一种方法,但在实践中它们具有可怕的常数因子,与对数的东西相比,这些常数因素是显着的。
答案 3 :(得分:0)
这是一个很大的问题,而像CLRS这样的算法书籍却一无所获而浮出水面。
有几种方法可以解决此问题,或者:
decreaseKey
操作的自定义堆实现肯定使用了选项#1。例如,如果您熟悉OpenSourceRoutingMachine(OSRM),它将在具有数百万个节点的图形上搜索以计算道路路线方向。它使用Boost implementation中的d-ary heap特别是因为它具有更好的decreaseKey
操作source。通常也为此目的提到Fibonacci_heap,因为它支持O(1)
减少键操作,但是同样,您可能必须自己滚动。
在选项2中,您最终总共要进行更多的插入操作和removeMin
个操作。如果D
是必须执行的“放松”操作的总数,那么最终将总共进行另外D
个堆操作。因此,尽管从理论上讲,它的运行时复杂度较差,但实际上,有研究证据表明,选项#2可以具有更高的性能,因为您可以利用缓存局部性并避免保留指针来执行操作的额外开销。 decreaseKey
个操作(请参阅[1],特别是第16页)。这种方法还具有更简单的优点,并允许您使用大多数语言的标准库堆/优先级队列实现。
为您提供一些有关选项2外观的伪代码:
// Imagine this is some lookup table that has the minimum weight
// so far for each node.
weights = {}
while Queue is not empty:
u = Queue.removeMin()
// This is our new logic to discard the duplicate entries.
if u.weight > weights[u]:
continue
visit neighbors[u] and relax() each one
作为替代方案,您还可以查看Python标准库heapq docs,它描述了另一种跟踪堆中“死”条目的方法。您是否认为它有用吗取决于您用于图形表示和存储顶点距离的数据结构。