为啥 Dijkstra 的算法不适用于负权重边缘?

Posted

技术标签:

【中文标题】为啥 Dijkstra 的算法不适用于负权重边缘?【英文标题】:Why doesn't Dijkstra's algorithm work for negative weight edges?为什么 Dijkstra 的算法不适用于负权重边缘? 【发布时间】:2012-10-20 23:50:27 【问题描述】:

谁能告诉我为什么 Dijkstra 的单源最短路径算法假定边必须是非负的。

我说的只是边缘而不是负重量循环。

【问题讨论】:

一个很好的例子的正确答案是:***.com/questions/6799172/… 【参考方案1】:

回想一下,在 Dijkstra 的算法中,一旦一个顶点被标记为“封闭”(并且在开放集之外) - 算法会找到到它的最短路径,并且永远不需要开发这个再次节点 - 它假设开发到该路径的路径是最短的。

但如果权重为负 - 这可能不是真的。例如:

       A
      / \
     /   \
    /     \
   5       2
  /         \
  B--(-10)-->C

V=A,B,C ; E = (A,C,2), (A,B,5), (B,C,-10)

A的Dijkstra会先开发C,后来找不到A->B->C


EDIT更深入的解释:

请注意,这很重要,因为在每个松弛步骤中,算法假定“关闭”节点的“成本”确实是最小的,因此接下来将选择的节点也是最小的。

它的想法是:如果我们有一个开放的顶点,它的成本是最小的——通过将任何正数添加到任何顶点——最小性永远不会改变。 如果没有正数的限制 - 上述假设不成立。

由于我们确实“知道”每个“关闭”的顶点是最小的 - 我们可以安全地执行松弛步骤 - 而无需“回头看”。如果我们确实需要“回头看”——Bellman-Ford 提供了一个类似递归 (DP) 的解决方案。

【讨论】:

抱歉,我没有收到任何错误。首先A->B 将是5,A->C 将是2。然后B->C-5。所以C 的值将是-5,与bellman-ford 相同。这怎么没有给出正确的答案? @tintinmj 首先,Dijkstra 将“关闭”值为 0 的节点 A。然后,它将查看最小值节点,B 为 5,C 为 2。最小是C,所以它将关闭C,值为2并且永远不会回头,当稍后关闭B时,它不能修改C的值,因为它已经“关闭”了。跨度> @amit Dijkstra 的算法如何找不到路径A -> B -> C?它将首先将C 的距离更新为2,然后将B 的距离更新为5。假设在您的图中没有来自C 的出边,那么我们在访问C 时什么也不做(和它的距离仍然是2)。然后我们访问D的相邻节点,唯一的相邻节点是C,新的距离是-5。请注意,在 Dijkstra 算法中,我们还跟踪我们到达(并更新)节点的父节点,并从C 开始,您将获得父节点B,然后是A,导致一个正确的结果。我错过了什么? @amit 你的推理问题(我认为),我见过其他人这样做(奇怪),是你认为算法不会重新考虑已经确定最短距离的节点(并且我们已经完成了),但这不正确,这就是我们有“松弛”步骤的原因......我们遍历图形的所有节点,并且对于每个节点,我们遍历相邻节点,例如,即使任何相邻节点可能已经从我们的最小优先级队列中删除。 @amit 检查这个类似问题的答案,这个例子实际上是有意义的:***.com/a/6799344/3924118【参考方案2】:

考虑下面显示的图表,源为顶点 A。首先尝试自己在其上运行 Dijkstra 的算法。

当我在解释中提到 Dijkstra 算法时,我将讨论如下实现的 Dijkstra 算法,

所以从最初分配给每个顶点的从源到顶点的距离)开始,

我们首先提取Q = [A,B,C]中具有最小值的顶点,即A,然后Q = [B, C]。注意 A 对 B 和 C 有一个有向边,而且它们都在 Q 中,因此我们更新这两个值,

现在我们将 C 提取为 (2Q = [B]。请注意,C 未连接任何内容,因此 line16 循环不会运行。

最后我们提取 B,然后是 。注意 B 对 C 有一个有向边,但 C 在 Q 中不存在,因此我们再次不在 line16 中输入 for 循环,

所以我们最终得到的距离为

注意这是错误的,因为从 A 到 C 的最短距离是 5 + -10 = -5,当您转到 时。

所以对于这个图,Dijkstra 算法错误地计算了从 A 到 C 的距离。

发生这种情况是因为 Dijkstra 算法没有尝试找到通向 已经从 Q 中提取的顶点的更短路径

line16 循环正在做的是获取顶点 u 并说 “嘿,看起来我们可以通过 从源转到 v u,这个(替代或替代)距离是否比我们得到的当前 dist[v] 更好?如果是这样,让我们​​更新 dist[v]"

请注意,在line16 中,他们检查 u 的所有邻居 v(即存在从 u 到 v 的有向边),其中仍处于 Q。在line14 中,他们从 Q 中删除了已访问的注释。因此,如果 xu 的已访问邻居,则路径 甚至不考虑 作为从源到 v 的一种可能更短的方式。

在我们上面的例子中,C 是 B 的访问邻居,因此不考虑路径 ,保持当前最短路径 不变。

这实际上很有用如果边权重都是正数,因为这样我们就不会浪费时间考虑不能更短的路径。 p>

所以我说,当运行这个算法时,如果 xy 之前从 Q 中提取出来,那么就不可能找到更短的路径 - 。让我用一个例子来解释一下,

由于 y 刚刚被提取,并且 x 已经在其自身之前被提取,因此 dist[y] > dist[x] 因为否则y 会在 x 之前被提取。 (line 13 最小距离优先)

正如我们已经假设边缘权重是正的,即length(x,y)>0。所以通过 y 的替代距离 (al​​t) 总是肯定会更大,即 dist[y] + length(x,y)> dist[x]。因此,即使 y 被认为是到 x 的路径,dist[x] 的值也不会被更新,因此我们得出结论:仅考虑仍在 Q 中的 y 的邻居是有意义的(注意line16 中的评论)

但这取决于我们对正边长度的假设,如果 length(u,v) 则取决于该边的负值程度,我们可能会替换 dist[x] line18 比较之后。

因此,如果在所有顶点 v 之前删除 x,我们所做的任何 dist[x] 计算都是不正确的 - 这样 xv 的邻居,负边连接它们 - 被移除。

因为这些 v 顶点中的每一个都是从源到 x 的潜在“更好”路径上的倒数第二个顶点,这被 Dijkstra 算法丢弃。

所以在我上面给出的例子中,错误是因为 C 在 B 被删除之前被删除了。而那个 C 是 B 的邻居,有一个负边缘!

澄清一下,B 和 C 是 A 的邻居。 B有一个邻居C,C没有邻居。 length(a,b) 是顶点 a 和 b 之间的边长。

【讨论】:

就像你说的,解决这个问题的更好方法是在每次比较后使用 heapq.heappush 方法。我们将更新后的距离推回队列中。在这种情况下,Dijkstra 可以在负权重上工作。我试了一下,结果是0,5,-5 "甚至不考虑 x 到 u 的路径源";你的意思是从 u 到 x 的来源吗? @slmatrix 感谢您发现这一点,是的,我的意思是从源到 u 到 x 的路径,因为 x 是 u 的邻居。 实际上,在代码的第 16 行中,没有 v 应该在 Q 中的约束(唯一的“约束”在注释中),所以你的例子失败了。具体来说,在您的解释中,“注意 B 对 C 有有向边,但 Q 中不存在 C,因此我们再次不在第 16 行进入 for 循环”这一行是错误的,我们实际上再次进入循环并更新最后一条边,因此 B = 1。因此,Dijkstra 算法在您的示例中正确运行。 @ДенисКокорев 确切地说,这让我很困惑,因为只有评论提到了这一点,而算法实际上并没有这样做。【参考方案3】:

Dijkstra 的算法假设路径只能变得“更重”,因此,如果您有一条从 A 到 B 的路径的权重为 3,而从 A 到 C 的路径的权重为 3,则您无法添加一条边,通过C从A到B,权重小于3。

这种假设使算法比必须考虑负权重的算法更快。

【讨论】:

【参考方案4】:

Dijkstra 算法的正确性:

在算法的任何步骤中,我们都有 2 组顶点。集合 A 由我们计算到的最短路径的顶点组成。集合 B 由剩余的顶点组成。

归纳假设:在每一步,我们都会假设所有之前的迭代都是正确的。

归纳步骤:当我们在集合 A 中添加一个顶点 V 并设置距离为 dist[V] 时,我们必须证明这个距离是最优的。如果这不是最优的,那么必须有一些其他路径到顶点 V 的长度更短。

假设这条其他路径经过某个顶点 X。

现在,由于 dist[V]

因此,要使 dijkstra 算法起作用,边权重必须为非负数。

【讨论】:

【参考方案5】:

在下图中尝试 Dijkstra 算法,假设 A 是源节点,D 是目标,看看发生了什么:

请注意,您必须严格遵循算法定义,而不应遵循直觉(直觉告诉您上面的路径较短)。

这里的主要观点是,该算法只查看所有直接连接的边,并采用这些边中最小的一条。该算法不向前看。您可以修改此行为,但它不再是 Dijkstra 算法了。

【讨论】:

抱歉,我没有收到任何错误。首先A->B1A->C100。然后B->D2。然后C->D-4900。所以D 的值将是-4900 与bellman-ford 相同。这怎么没有给出正确的答案? @tintinmj 如果你有来自 D 的出边,它将在 D 的距离减小之前被访问,因此在它之后不会更新。这肯定会导致错误。如果您在扫描出边后已经将 D 的 2 视为最终距离,则即使此图也会导致错误。 @tb- 很抱歉问了这么久,但是,我在正确的轨道上吗?首先A->B 将是1A->C 将是100。然后探索B 并将B->D 设置为2。那么 D 被探索是因为目前它有返回源的最短路径吗?如果B->D100,我会正确地说C 会首先被探索吗?我理解人们给出的所有其他例子,除了你的。 @PejmanPoh 据我了解,如果 B->D 为 100,由于 A->C 在将要使用的 HeapStructure 中更高,因此 extract min 将首先返回 A->C,这意味着下一个找到的最短路径将是到 C 的路径,之后从 C->D 的路径(权重 -5000)将是显而易见的选择,从而得出最短路径将是从 A->C-> 的结论D,我很确定这将是正常行为。所以有时当我们有负循环时,我们仍然可能得到最短路径的正确值,但绝对不是总是这样,这是一个我们不会的例子.. 好吧,如果你有一个总权重为负的循环,那么严格来说没有最短路径。因为对于每个声称是最短路径的路径 P,您可以通过包含一个额外的循环迭代来找到更短的路径 P'。【参考方案6】:

Dijkstra 的算法假设所有边都是正加权的,这个假设有助于算法运行得更快 (O(E*log(V)) 比其他考虑到的负边缘的可能性(例如复杂度为 O(V^3) 的贝尔曼福特算法)。

该算法在以下情况(带有 -ve 边)中不会给出正确的结果,其中 A 是源顶点:

这里,从源 A 到顶点 D 的最短距离应该是 6。但根据 Dijkstra 的方法,最短距离应该是 7,这是不正确的。

此外,Dijkstra 算法有时可能会给出正确的解决方案,即使存在负边。下面是这种情况的一个例子:

但是,它永远不会检测到负循环,并且总是会产生一个结果,它总是不正确如果负权重循环可以从源到达,因为在这种情况下不存在最短路径来自源顶点的图。

【讨论】:

【参考方案7】:

回想一下,在 Dijkstra 的算法中,一旦一个顶点被标记为“关闭”(并且在开放集之外)-它假设任何源自它的节点都会导致更大的距离,所以,算法找到了它的最短路径,并且永远不必再次开发这个节点,但是在负权重的情况下这并不成立。

【讨论】:

【参考方案8】:

您可以在不包括负循环的负边上使用 dijkstra 算法,但您必须允许一个顶点可以被多次访问,并且该版本将失去它的快速时间复杂度。

在这种情况下,实际上我发现最好使用SPFA algorithm,它有正常的队列并且可以处理负边缘。

【讨论】:

【参考方案9】:

到目前为止,其他答案很好地证明了为什么 Dijkstra 的算法无法处理路径上的负权重。

但问题本身可能是基于对路径权重的错误理解。如果通常在寻路算法中允许路径上的负权重,那么您将获得不会停止的永久循环。

考虑一下:

A  <- 5 ->  B  <- (-1) ->  C <- 5 -> D

A 和 D 之间的最佳路径是什么?

任何寻路算法都必须在 B 和 C 之间不断循环,因为这样做会减少总路径的权重。因此,为连接允许负权重将使任何 pathfindig 算法都没有实际意义,除非您将每个连接限制为仅使用一次。

因此,为了更详细地解释这一点,请考虑以下路径和权重:

Path               | Total weight
ABCD               | 9
ABCBCD             | 7
ABCBCBCD           | 5
ABCBCBCBCD         | 3
ABCBCBCBCBCD       | 1
ABCBCBCBCBCBCD     | -1
...

那么,完美的路径是什么?每当算法添加BC 步骤时,它都会将总权重减少 2。

因此最佳路径是A (BC) D,其中BC 部分将永远循环。

由于 Dijkstra 的目标是找到最优路径(不仅仅是任何路径),根据定义,它不能使用负权重,因为它无法找到最优路径。

Dijkstra 实际上不会循环,因为它保留了它访问过的节点列表。但它不会找到完美的路径,而是随便找一条路径。

【讨论】:

不,因为最优路径会在 B 和 C 之间不断跳转。想象一下:算法已经找到这条路径:A B C。当前权重为 4 (5-1)。现在在 C 处,它可以到达 D,总权重为 9。但如果它返回 B,它会找到权重为 7 的路径 ABCBCD,这样更好。但话又说回来,它可能需要权重为 5 的 ABCBCBCD。所以本质上,如果你想找到最佳路径,就会有一个无限循环。负权重的最优路径是 A (BC) D,其中 BC 部分无休止地重复。 对于负权重,Dijkstra 可能会找到 A 路径,但找不到最优路径(因为负权重在物理上不可能找到最优路径)。因此 Dijkstra(与任何其他寻路算法一样)无法“工作”,因为它是一条完美的路径。【参考方案10】:

在前面的答案之上,为以下简单示例添加几点说明,

    Dijktra 的算法是贪心的,它首先贪婪地找到距离源顶点A的最小距离顶点C,并分配距离d[C](从顶点A)到边AC的权重。 基本假设是,由于首先选择了 C,因此图 s.t. 中没有其他顶点 Vw(AV) ,否则算法会选择 V 而不是 C 由于上述逻辑,w(AC) ,对于所有不同于顶点 A 的顶点 VC。现在,显然任何其他路径 PA 开始并以 C 结束,经过 V ,即路径 P = A -> V -> ... -> C 的长度会更长(>= 2),路径 P 的总成本将为其上的边的总和,即 cost(P) >= w(AV) >= w(AC),假设 P 上的所有边都有 non -负权重,所以 C 可以安全地从队列 Q 中移除,因为在此假设下,d[C] 永远不会变得更小/更宽松。 显然,当 P 上的 some.edge 为负时,上述假设不成立,在这种情况下 d[C] 可能会进一步减小,但算法可以'不要处理这种情况,因为到那时它已经从队列 Q 中删除了 C

【讨论】:

以上是关于为啥 Dijkstra 的算法不适用于负权重边缘?的主要内容,如果未能解决你的问题,请参考以下文章

Dijkstra 算法的负边缘

为啥全对最短路径算法使用负权重?

为啥这个 Dijkstra 算法不适用于这个特定的输入?

在非加权图中找到最短路径

Floyd最短路(带路径输出)

单源最短路径Dijkstra算法