使用Dijkstra算法的负权重

时间:2011-07-23 08:19:15

标签: algorithm dijkstra shortest-path graph-algorithm

我试图理解为什么Dijkstra的算法不适用于负权重。阅读Shortest Paths上的示例,我试图找出以下场景:

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

来自网站:

  

假设边缘全部从左向右,如果我们开始   使用A,Dijkstra的算法将选择边缘(A,x)最小化   d(A,A)+长度(边缘),即(A,B)。然后设置d(A,B)= 2并选择   另一个边缘(y,C)最小化d(A,y)+ d(y,C);唯一的选择是(A,C)   它设定d(A,C)= 3。但它永远找不到从A到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)
}

谢谢,

梅尔

9 个答案:

答案 0 :(得分:186)

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

Figure of graph

假设边缘从左到右指向,如示例所示,

您的算法将按如下方式运行:

  1. 首先,您将d(A)设置为zero,将其他距离设置为infinity
  2. 然后展开节点A,将d(B)设置为1,将d(C)设置为zero,将d(D)设置为99 }。
  3. 接下来,展开C,不做任何净更改。
  4. 然后展开B,这无效。
  5. 最后,展开D,将d(B)更改为-201
  6. 请注意,尽管d(C)的最短路径长度为0,但C仍然是-200。 / strong>因此,在某些情况下,您的算法无法准确计算距离。此外,即使您要存储指示如何从每个节点到达起始节点A的指针,您也会以错误的路径从C返回到A。< / p>

答案 1 :(得分:19)

注意,如果图表没有负周期,即总计权重小于零的周期,Dijkstra甚至可以用于负权重。

当然有人可能会问,为什么在templatetypedef Dijkstra所做的例子中,即使没有负循环也没有失败,实际上甚至没有循环。这是因为他正在使用另一个停止标准,一旦达到目标节点就保持算法(或者所有节点已经安置一次,他没有确切地指定)。在没有负权重的图表中,这可以正常工作。

如果正在使用替代停止标准,当优先级队列(堆)运行为空时停止算法(此问题中也使用此停止标准),那么dijkstra即使对于具有负数的图形也会找到正确的距离重量但没有负循环。

然而,在这种情况下,没有负循环的图的dijkstra的渐近时间界限将丢失。这是因为当由于负权重而找到更好的距离时,可以将先前建立的节点重新插入堆中。此属性称为标签更正。

答案 2 :(得分:9)

你没有在算法的任何地方使用S(除了修改它)。 dijkstra的想法曾经是一个顶点在S上,它将不再被修改。在这种情况下,一旦B在S内,您将无法通过C再次到达。

这个事实确保了O(E + VlogV)的复杂性[否则,你将重复边多次一次,顶点多一次]

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

答案 3 :(得分:6)

由于Dijkstra是一种贪婪的方法,一旦顶点被标记为此循环的访问,即使有另一条路径以较低的成本稍后到达它,它也永远不会再次重新评估。这种问题只有在图中存在负边时才会发生。


一个贪心算法,顾名思义,总是做出那个时刻似乎最好的选择。假设你有一个需要的目标函数在给定点优化(最大化或最小化)。 贪婪算法在每个步骤中做出贪婪的选择,以确保优化目标函数。 贪婪算法只有一次计算最佳解决方案,以便它永远不会返回并反转决策。

答案 4 :(得分:1)

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

(仅在未指示图表时相关)

编辑: 我认为这个问题与AC *的路径只能比负面权重边缘的AB更好的事实有关,因此在AC之后去哪里并不重要,假设非负权重一旦你选择在进入AC后达到B,就不可能找到比AB好的路径。

答案 5 :(得分:1)

“2)我们可以将Dijksra算法用于具有负权重的图的最短路径 - 一个想法可以是,计算最小权重值,将正值(等于最小权重值的绝对值)添加到所有权重并运行Dijksra的修改图算法。这个算法会起作用吗?“

除非所有最短路径具有相同的长度,否则这绝对不起作用。例如,给定两条边长的最短路径,并且在向每条边添加绝对值之后,总路径成本增加2 * | max负权重|。另一方面,另一条长度为三条边的路径,因此路径成本增加了3 * |最大负重量|。因此,所有不同的路径都会增加不同的数量。

答案 6 :(得分:0)

TL; DR:对于您发布的伪代码,它的权重为负。


Dijkstra算法的变体

关键是 Dijkstra算法有3种版本,但该问题下的所有答案都忽略了这些变体之间的差异。

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

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

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

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

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

     

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


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

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

我将结合所有评论以更好地理解这个问题。

有两种使用 Dijkstra 算法的方法:

  1. 标记已经找到与源的最小距离的节点(更快的算法,因为我们不会重新访问已经找到最短路径的节点)

  2. 不标记已经找到离源最小距离的节点(比上面慢一点)

现在问题来了,如果我们不标记节点以便我们可以找到最短路径,包括那些包含负权重的路径会怎样?

答案很简单。考虑图中只有负权重的情况:

graph containing neg. cycle)

现在,如果您从节点 0()开始,您将有如下步骤(这里我没有标记节点):

  1. 0->0 as 0, 0->1 as inf , 0->2 as inf 开头

  2. 0->1 为 -1

  3. 0->2 为 -5

  4. 0->0 为 -8(因为我们没有放松节点)

  5. 0->1 为 -9 .. 依此类推

这个循环将永远持续下去,因此 Dijkstra 算法无法找到负权重的最小距离(考虑所有情况)。

这就是为什么 Bellman Ford Algo 用于在负权重的情况下找到最短路径的原因,因为它会在负循环的情况下停止循环。