为什么Dijkstra的算法使用减少键?


93

Dijkstra的算法教给我如下

while pqueue is not empty:
    distance, node = pqueue.delete_min()
    if node has been visited:
        continue
    else:
        mark node as visited
    if node == target:
        break
    for each neighbor of node:
         pqueue.insert(distance + distance_to_neighbor, neighbor)

但是我一直在阅读有关算法的文章,我看到很多版本都使用reduce-key而不是insert。

为什么会这样?两种方法有何区别?


14
Downvoter-您能解释这个问题怎么了吗?我认为这是完全公平的,许多人(包括我在内)首先被介绍给Dijkstra的OP版本,而不是减小键版本。
templatetypedef

Answers:


68

使用减少密钥而不是重新插入节点的原因是要使优先级队列中的节点数保持较小,从而使优先级队列出队的总数保持较小,并且每个优先级队列平衡的成本较低。

在Dijkstra算法的实现中,该算法将节点及其新的优先级重新插入到优先级队列中,对于图中的m个边中的每条边,将一个节点添加到优先级队列中。这意味着优先级队列上有m个入队操作和m个出队操作,因此总运行时间为O(m T e + m T d),其中T e是进入优先级队列所需的时间,而T d是从优先级队列中出队所需的时间。

在支持减少密钥的Dijkstra算法的实现中,保存节点的优先级队列以其中的n个节点开始,并且在算法的每一步中都将删除一个节点。这意味着堆出队的总数为n。每个节点可能会为进入它的每个边调用一次减少键,因此减少键的总数最多为m。这给出了(n T e + n T d + m T k)的运行时间,其中T k是调用减少键所需的时间。

那么这对运行时有什么影响?这取决于您使用的优先级队列。这是一张快速表格,显示了不同的优先级队列以及不同的Dijkstra算法实现的整体运行时间:

Queue          |  T_e   |  T_d   |  T_k   | w/o Dec-Key |   w/Dec-Key
---------------+--------+--------+--------+-------------+---------------
Binary Heap    |O(log N)|O(log N)|O(log N)| O(M log N)  |   O(M log N)
Binomial Heap  |O(log N)|O(log N)|O(log N)| O(M log N)  |   O(M log N)
Fibonacci Heap |  O(1)  |O(log N)|  O(1)  | O(M log N)  | O(M + N log N)

如您所见,对于大多数类型的优先级队列,渐近运行时确实没有什么区别,并且减小键版本不太可能做得更好。但是,如果您使用优先级队列的Fibonacci堆实现,那么在使用reduce-key时,确实Dijkstra的算法在渐近效率上会更高。

简而言之,使用减少键和良好的优先级队列可以使Dijkstra的渐近运行时间超出您继续排队和出队的可能性。

除此之外,还有一些更高级的算法,例如Gabow的最短路径算法,使用Dijkstra的算法作为子例程,并严重依赖于reduce键的实现。他们使用这样的事实:如果您事先知道有效距离的范围,则可以基于该事实构建超高效的优先级队列。

希望这可以帮助!


1
+1:我忘记考虑堆了。一个小问题,因为插入版本的堆在每个边上都有一个节点,即O(m),它的访问时间是否应为O(log m),从而得出总运行时间为O(m log m)?我的意思是,在正态图中m不大于n ^ 2,所以它减小为O(m log n),但是在其中两个节点可能由不同权重的多个边连接在一起的图中,m是无界的(当然,我们可以断言两个节点之间的最小路径仅使用最小的边缘,并将其减少为普通图,但是对于随机数,这很有趣。

2
@ rampion-的确有一点,但是由于我认为通常认为在触发算法之前已减少了平行边,所以我认为O(log n)与O(log m)无关紧要。通常假定m为O(n ^ 2)。
templatetypedef'2

27

在2007年,有一篇论文研究了使用减少键版本和插入版本之间执行时间的差异。参见http://www.cs.utexas.edu/users/shaikat/papers/TR-07-54.pdf

他们的基本结论是,大多数图形都不使用减少键。特别是对于稀疏图,非减小键比减小键版本快得多。有关更多详细信息,请参见本文。



警告:链接的文件中有错误。页16,功能B.2:if k < d[u]应该为if k <= d[u]
Xeverous

2

有两种实现Dijkstra的方法:一种使用支持​​减少键的堆,另一种使用不支持减少键的堆。

它们通常都有效,但是通常是首选。在下文中,我将使用'm'表示边的数量,使用'n'表示图形的顶点的数量:

如果您想要最大可能的最坏情况下的复杂性,则可以使用支持减少键的Fibonacci堆:您将获得一个不错的O(m + nlogn)。

如果您关心平均情况,那么也可以使用Binary堆:您将获得O(m + nlog(m / n)logn)。证明在这里,第99/100页。如果图是密集的(m >> n),则该图和前一个图都趋于O(m)。

如果你想知道你真实的图形运行它们会发生什么,你可以检查这个文件,马克Meketon在他的回答提出。

实验结果将表明,在大多数情况下,“更简单”的堆将提供最佳结果。

实际上,在使用减少键的实现中,Dijkstra在使用简单的Binary堆或Pairing堆时比在使用Fibonacci堆时表现更好。这是因为斐波那契堆涉及较大的常数因子,并且减少键操作的实际数量往往比最坏情况所预测的要少得多。

出于类似的原因,不必支持减键操作的堆,其常量因子甚至更少,并且实际上性能最佳。特别是在图形稀疏的情况下。

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.