使用Dijkstra算法的负权重


113

我试图理解为什么Dijkstra的算法不能在负权重下工作。阅读有关最短路径的示例,我试图找出以下情况:

    2
A-------B
 \     /
3 \   / -2
   \ /
    C

从网站:

假设所有边缘都是从左到右定向的,如果我们从A开始,Dijkstra的算法将选择使d(A,A)+ length(edge)最小的边缘(A,x),即(A,B)。然后设置d(A,B)= 2并选择另一个使d(A,y)+ d(y,C)最小的边(y,C); 唯一的选择是(A,C),它设置d(A,C)= 3。但是,它永远找不到从A到B的最短路径,即C,总长度为1。

我不明白为什么使用下面的Dijkstra实现时,d [B]不会更新为1(当算法到达顶点C时,它将在B上运行放松,看到d [B]等于2,因此更新其值为1)。

Dijkstra(G, w, s)  {
   Initialize-Single-Source(G, s)
   S ← Ø
   Q ← V[G]//priority queue by d[v]
   while Q ≠ Ø do
      u ← Extract-Min(Q)
      S ← S U {u}
      for each vertex v in Adj[u] do
         Relax(u, v)
}

Initialize-Single-Source(G, s) {
   for each vertex v  V(G)
      d[v] ← ∞
      π[v] ← NIL
   d[s] ← 0
}

Relax(u, v) {
   //update only if we found a strictly shortest path
   if d[v] > d[u] + w(u,v) 
      d[v] ← d[u] + w(u,v)
      π[v] ← u
      Update(Q, v)
}

谢谢,

梅尔


通常,使用负边缘权重进行寻路非常困难。无论您找到哪种路线,总有一条任意长的路线可能会在其某处出现任意较大的负边权重。如果它是NP完成的,我不会感到惊讶。
尼克·约翰逊

4
对于任何对此有疑问的人,您可以在图表中找到最短路径,因为它没有负重量周期。如果放松功能实际上成功完成时,如果放松功能返回“ true”值,则上述算法将起作用,在这种情况下,如果不存在,则相邻顶点“ v”将进入优先级队列,如果已经存在,则将其更新。这意味着当访问的节点保持放松状态时,它们可以再次添加到优先级队列中。
goelakash

Answers:


202

您所建议的算法确实会在该图中找到最短路径,但通常不会找到所有图。例如,考虑以下图形:

图图

假设您的示例中的边缘是从左到右,

您的算法将按以下方式工作:

  1. 首先,将设置d(A)zero,将其他距离设置为infinity
  2. 然后,展开了节点A,设置d(B)1d(C)zerod(D)99
  3. 接下来,展开C,没有任何净变化。
  4. 然后B,您展开,这没有效果。
  5. 最后,展开D,将其更改d(B)-201

请注意,尽管到最后,这d(C)仍然是0即使最短的路径C具有length -200 因此,在某些情况下,您的算法无法准确计算距离。而且,即使您要存储返回指针,说明如何从每个节点到达起始节点A,也将结束从错误路径返回C到的错误A


35
要补充您的出色答案:Dijkstra是一种贪婪算法,是其短视选择的原因。
blubb 2011年

4
我想指出的是,从技术上讲,该图中的所有路径都具有负无穷大的代价,这要归功于负循环A,D,B,A。
Nate

2
@ Nate-为清楚起见,图中的所有边都是从左到右。在我的高质量ASCII艺术中渲染箭头有点困难。:-)
templatetypedef

2
对于那些以前从未看到过带有负边的图表的人,我发现此图表的有用解释是收费公路网络,边权重决定了您所支付的通行费。-300路是一条疯狂的向后收费公路,在那儿他们给您$ 300。
D Coetzee

3
@ SchwitJanwityanujit-这就是Dijkstra算法的工作原理。该算法不探索路径,而是通过处理节点起作用。每个节点仅被处理一次,因此一旦我们处理B节点并获得其距离为1,我们将永远不会重新访问节点B或尝试更新其距离。
templatetypedef

25

注意,如果图没有负循环,即权重总和小于零的循环,则Dijkstra甚至适用于负权重。

当然,可能会问,即使没有负循环,实际上没有循环,为什么在templatetypedef Dijkstra的示例中失败了。那是因为他使用的是另一个停止标准,一旦到达目标节点(或所有节点都已安定一次,他没有完全指定该标准),该标准就立即保留该算法。在没有负权重的图形中,这可以正常工作。

如果使用替代停止准则,则当优先级队列(堆)为空时将停止算法(此停止准则也在问题中使用),则dijkstra即使对于权重为负但没有负值的图也将找到正确的距离负循环。

但是,在这种情况下,对于没有负周期的图,dijkstra的渐近时间范围丢失了。这是因为当由于负权重而发现更好的距离时,可以将先前沉降的节点重新插入堆中。此属性称为标签校正。


2.目前尚不清楚,为什么您认为时间会令我“更像贝尔曼·福特”而不是指数级(比贝尔曼·福特差)。您有具体的算法和证明吗?
加沙2014年

3
对于1 .:由于您可以使用上述停止条件使用完全相同的dijkstra实现,即当队列为空时停止运行(请参阅原始问题中的伪代码),即使行为不同,它仍然是最短路径的dijkstras算法使节点稳定几次(标签更正)。
infty10000101 2014年

1
到2 .:那只是个猜测,所以我将其删除。我认为您使用指数时间是正确的,因为有成千上万的路径需要探索。
infty10000101 2014年

11

您没有在算法的任何地方使用S(除了对其进行了修改)。dijkstra的想法是一旦顶点位于S上,就不会再对其进行修改。在这种情况下,一旦B在S内,您就不会再通过C到达它。

这个事实确保了O(E + VlogV)的复杂度[否则,您将重复边缘一次以上,而顶点重复一次以上]

换句话说,您发布的算法可能不像dijkstra的算法所承诺的那样在O(E + VlogV)中。


同样,无需修改没有负权重边的顶点,这完全打破了路径成本只能随着重复边而增加的假设
prusswan 2011年

这个假设正是使我们能够使用S的前提,一旦“知道”顶点在S中,就永远不会再对其进行修改。
艾米特

您最后的陈述是错误的。当发布的算法在没有负边的图形上工作时,其时间复杂度为O(E + VlogV)。不需要检查我们是否已经访问过节点,因为已经访问过该节点的事实保证了放宽过程不会在队列中再增加一次。
皮克斯

7

由于Dijkstra是贪婪方法,因此一旦将顶点标记为已访问此循环,即使以后再有一条成本较低的途径,也永远不会重新评估它。而且,只有当图形中存在负边缘时,才会发生这种问题。


一个贪心算法,顾名思义,总是让这似乎是在那一刻的最佳选择。假设您有一个目标函数,需要在给定点进行优化(最大化或最小化)。贪婪算法会在每个步骤中做出贪婪的选择,以确保优化目标函数。贪婪算法只有一次可以计算出最佳解决方案,因此它永远不会退缩并扭转决策。


4

TL; DR:答案取决于您的实现。对于您发布的伪代码,它具有负权重。


Dijkstra算法的变体

关键是Dijkstra算法有3种实现方式,但是这个问题下的所有答案都忽略了这些变体之间的差异。

  1. 使用嵌套for循环放松顶点。这是实现Dijkstra算法的最简单方法。时间复杂度为O(V ^ 2)。
  2. 基于优先级队列/堆的实现+不允许重入,其中重入意味着可以将松弛的顶点再次推入优先级队列,以便稍后再放松
  3. 基于优先级队列/堆的实现+允许重新进入。

版本1和版本2在权重为负的图形上将失败(如果您在这种情况下获得正确答案,那只是一个巧合),但是版本3仍然有效

在原始问题下发布的伪代码是上面的版本3,因此它具有负权重。

这是算法(第4版)的一个很好的参考,它说(并包含上面提到的版本2和3的Java实现):

问:Dijkstra的算法是否可以使用负权重?

答:是和否。有两种最短路径算法称为Dijkstra算法,具体取决于是否可以将一个顶点多次入队到优先级队列中。当权重为非负数时,这两个版本会重合(因为没有一个顶点会被多次排队)。在存在负边权重(但不包含负循环)的情况下,在DijkstraSP.java中实现的版本(允许顶点多次入队)是正确的,但在最坏的情况下其运行时间是指数的。(我们注意到,如果边缘加权的有向图的边的权重为负,则DijkstraSP.java会引发异常,这样程序员就不会对这种指数行为感到惊讶。)如果我们修改DijkstraSP.java使得顶点无法入队不止一次(例如,使用marked []数组标记那些已经放松的顶点),


有关更多实现细节以及版本3与Bellman-Ford算法的连接,请参见zhihu的答复。这也是我的答案(但中文)。目前,我没有时间将其翻译成英文。如果有人可以这样做并在stackoverflow上编辑此答案,我将非常感激。


1

考虑一下如果您在B和C之间来回走动会发生什么...瞧

(仅在图形未定向时相关)

编辑:我认为问题与以下事实有关:使用AC *的路径只能在存在负权重边缘的情况下优于AB,因此,假设非如果您选择负重边,一旦选择AC后到达B,就不可能找到比AB更好的路径。


这是不可能的,图形是有向的。
艾米特

@amit:好点,我错过了。是时候重新考虑这个问题了
prusswan 2011年

1

“ 2)我们可以使用Dijksra算法为权重为负的图形提供最短路径吗?一个想法是,计算最小权重值,对所有权重添加一个正值(等于最小权重值的绝对值),并运行Dijksra算法修改后的图。此算法有效吗?”

除非所有最短的路径都具有相同的长度,否则这绝对是行不通的。例如,给定一条长度为两条边的最短路径,并在为每条边加上绝对值之后,总路径成本将增加2 * || max negative weight |。另一方面,另一条路径的长度为三个边缘,因此路径成本增加了3 * || max negative weight |。因此,所有不同的路径都会增加不同的数量。


0

您可以对不包含负循环的负边缘使用dijkstra算法,但必须允许顶点可以多次访问,而该版本将失去快速的时间复杂性。

在那种情况下,实际上我已经看到使用SPFA算法更好,该算法具有正常的队列并且可以处理负边缘。

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.