【问题标题】:Can we change Dijkstra's Algorithm to work with negative weights?我们可以改变 Dijkstra 的算法以使用负权重吗?
【发布时间】:2012-05-29 13:15:07
【问题描述】:

取自维基百科的伪代码:

function Dijkstra(Graph, source):
 2      for each vertex v in Graph:                                // Initializations
 3          dist[v] := infinity ;                                  // Unknown distance function from source to v
 4          previous[v] := undefined ;                             // Previous node in optimal path from source
 5      end for ;
 6      dist[source] := 0 ;                                        // Distance from source to source
 7      Q := the set of all nodes in Graph ;                       // All nodes in the graph are unoptimized - thus are in Q
 8      while Q is not empty:                                      // The main loop
 9          u := vertex in Q with smallest distance in dist[] ;    // Start node in first case
10          if dist[u] = infinity:
11              break ;                                            // all remaining vertices are inaccessible from source
12          end if ;
13          remove u from Q ;
14          for each neighbor v of u:                              // where v has not yet been removed from Q.
15              alt := dist[u] + dist_between(u, v) ;
16              if alt < dist[v]:                                  // Relax (u,v,a)
17                  dist[v] := alt ;
18                  previous[v] := u ;
19                  decrease-key v in Q;                           // Reorder v in the Queue
20              end if ;
21          end for ;
22      end while ;
23      return dist[] ;
24  end Dijkstra.

现在,在第 14 行中,我们看到松弛仅应用于尚未从 Q 中删除的 u 的邻居。但是,如果我们还考虑已从 Q 中删除的 u 的邻居,在我看来,该算法确实适用于负权重。我没有发现任何与此说法相矛盾的实例。

那么为什么 Dijkstra 的算法没有这样改变呢?

【问题讨论】:

    标签: algorithm dijkstra directed-graph single-source


    【解决方案1】:

    您当然可以使 Dijkstra 的算法工作为负值,只需确保您不遍历任何节点或边两次。在这里,work 的意思是终止。然而,结果可能不是最佳的。

    Dijkstra 的算法对它有一种贪婪的感觉。想象一下下图:

    A --- 1 --- B
    |           |
    3          -4
    |           |
    C -- -1 --- D
    

    如果我们想从 A 到 B,最好的路径是 A-C-D-B,但 Dijkstra 的算法会找到 A-B。你不能让 Dijkstra 的算法预测未来,因为它是一个贪心算法。 预测未来我的意思是知道以后,路径的成本可能会降低。请注意,这意味着如果将修改应用于 Dijkstra 算法的一个版本,该版本会在看到目标后立即终止。在您发布的版本中,您的修改工作除了有更有效的方法来处理负边缘(见 cmets)。

    附带说明一下,具有负值的无向图或具有负循环的有向图中的最短路径甚至没有意义!

    【讨论】:

    • 这并没有回答问题,而是讨论了一种修改 Dijkstra 的不同方法,以使其终止具有非负边。问题是关于一个特定的修改,使它不仅terminate,而且work(当然,只要定义了结果)。
    • @mastov,这个答案首先告诉 OP,他的修改不足以使算法终止(因为负循环),并通过提及可以添加的内容使其终止来完成 OP 自己的建议.然后它会继续说明为什么这不是一个好主意,以及无论如何可能找不到最佳解决方案。
    • 换句话说,问题中的特定修改既不会终止也不会起作用(当然一般来说)。
    • 如果有解决方案,它会终止并工作。
    • @mastov,好的,但是有什么意义呢?首先,它将失去其良好的执行顺序(即使对于所有正图,它也会变慢,或者您可能想在检查是否存在负边之前放置一个if?)。其次,您没有事先检查是否有解决方案。这意味着如果没有解决方案,您将陷入无限循环。我可以提醒您,不一定终止的过程不是定义上的算法?此外,可能永远不会终止的过程是您最不想在图上使用的东西。
    【解决方案2】:

    Dijkstra 可以一次访问每个节点,因为当它选择要访问的新节点时,他会选择从​​根开始的路径最短的未访问节点。因此,他可以安全地假设没有更短的方法可以通过另一个未访问的节点到达该节点(因为如果您知道从 A 到 B 的最佳方式成本为 2,而从 A 到 C 的最佳方式成本为 3,没有机会找到从 A 到 B 的更好的方式,例如 A>C>B)。

    如果你添加负权重,你会突然打破这个假设。

    您当然可以使用您建议的修改,但是您将失去访问每个节点一次的好处;因此与其他算法(例如 Ford-Bellman)相比,它会失去性能增益

    【讨论】:

      【解决方案3】:

      你基本上有两种选择。

      1. 您可以根据自己的建议更改算法。如果你有没有负循环的有向图,那么这是一个正确的算法,但它可能真的很慢(因为你会多次访问每个节点)。我认为存在这种算法具有指数时间复杂度的情况。

      2. 您可以通过使用电位来更改边值成本。每个顶点都有一些潜在的 h(v),边 u->v 的权重将为 w(u,v) + h(u) - h(v)。请注意,这不会影响给定两个顶点 (s, t) 之间的哪条路径最短,只是它的成本会被 h(s) - h(t) 改变。 但是你需要计算潜力。这里建议这样做的好方法:http://en.wikipedia.org/wiki/Johnson's_algorithm

      【讨论】:

        【解决方案4】:

        不,不可能如前所述。除非您严格限制所提供的图形类型,否则该算法对负权重没有意义。

        假设一个图包含节点 A、B、C 和权重 AB=-1、BA=0、BC=1 的边。

        现在 A 和 C 之间不再存在最短路径,您总是可以通过在 A 和 B 之间再来回走一次来缩短路径。

        当然,该算法仍会运行,但它会给出错误答案

        【讨论】:

        • 经过上述修改的算法永远不会给出错误的答案。它给出了正确的答案,只要它存在(在你的情况下,最短路径的问题本身是未定义的)。否则它不会终止(可以通过迭代的上限轻松修复并抛出异常)。
        【解决方案5】:

        是的,您的修改将在您未提及的 2 个假设下起作用,但我猜这是隐含的:

        1. 必须在输入图中实际定义最短路径。如果你放弃对非负权重的限制,你至少必须要求没有负权重的循环。
        2. 第 19 行中的“减少 Q 中的键 v”操作对于不在 Q 中的节点 v 并没有真正意义。但是,当然,您可以使用新值将它们重新添加到 Q。

        但是,您会失去 Dijkstra 算法的一个重要特性:它具有良好的最坏情况渐近性能。现成的 Dijkstra 保证了良好的性能,因为它最多访问每个节点和每个边一次。包含您的更改后,已删除的节点可以重新添加到优先级队列中,并且可能必须一遍又一遍地访问图表的大部分。在最坏的情况下,您必须执行与 Bellman-Ford 算法一样多的松弛,但您有优先级队列的额外开销。这会使您在最坏情况下的性能比 Bellman-Ford 差,因此它更适用于带有负边的图。

        这并不意味着您修改后的 Dijkstra 没有用。如果负边很少和/或如果这些负边通过昂贵的路径与图的其余部分分开,它可以比 Bellman-Ford 表现得更好。但要为最糟糕的情况做好准备。

        【讨论】:

          【解决方案6】:

          好吧,仅仅放松 Dijkstra 修改版本中已经访问过的节点是不够的(它会在包含负边的图中给你一个错误的答案)。此外,您需要将任何宽松的边缘放入您的容器中(例如,优先级队列或仅队列)。因此,您可以将第 19 行的代码修改为:

          if v is in priority queue
              then decrease-key(v)
          else
              add v into priority queue
          

          在这种情况下,您的算法可以工作。此外,对于正加权图,修改版 Dijkstra 的效率不会降低。这很容易证明,因为这自然是一个贪心算法。

          但是,对于包含负边的图,新算法在理论分析方面可能会变慢,但在实践中会很好地工作。

          其实你可以看看段丁凡在中国(1994)发表的一种名为SPFA(Shortest Path Faster Algorithm)的算法。许多 OIer(信息奥林匹克)都知道这种算法有时可能会击败 Dijkstra。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-05-01
            • 2011-10-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-08-22
            • 2016-05-07
            • 1970-01-01
            相关资源
            最近更新 更多