【发布时间】:2012-10-20 23:50:27
【问题描述】:
谁能告诉我为什么 Dijkstra 的单源最短路径算法假定边必须是非负的。
我说的只是边缘而不是负重量循环。
【问题讨论】:
-
一个很好的例子的正确答案是:stackoverflow.com/questions/6799172/…
标签: algorithm shortest-path dijkstra greedy
谁能告诉我为什么 Dijkstra 的单源最短路径算法假定边必须是非负的。
我说的只是边缘而不是负重量循环。
【问题讨论】:
标签: algorithm shortest-path dijkstra greedy
回想一下,在 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 相同。这怎么没有给出正确的答案?
A。然后,它将查看最小值节点,B 为 5,C 为 2。最小是C,所以它将关闭C,值为2并且永远不会回头,当稍后关闭B时,它不能修改C的值,因为它已经“关闭”了。跨度>
A -> B -> C?它将首先将C 的距离更新为2,然后将B 的距离更新为5。假设在您的图中没有来自C 的出边,那么我们在访问C 时什么也不做(和它的距离仍然是2)。然后我们访问D的相邻节点,唯一的相邻节点是C,新的距离是-5。请注意,在 Dijkstra 算法中,我们还跟踪我们到达(并更新)节点的父节点,并从C 开始,您将获得父节点B,然后是A,导致一个正确的结果。我错过了什么?
当我在解释中提到 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 中删除了已访问的注释。因此,如果 x 是 u 的已访问邻居,则路径 甚至不考虑 作为从源到 v 的一种可能更短的方式。
在我们上面的例子中,C 是 B 的访问邻居,因此不考虑路径 ,保持当前最短路径 不变。
这实际上很有用如果边权重都是正数,因为这样我们就不会浪费时间考虑不能更短的路径。 p>
所以我说,当运行这个算法时,如果 x 在 y 之前从 Q 中提取出来,那么就不可能找到更短的路径 - 。让我用一个例子来解释一下,
由于 y 刚刚被提取,并且 x 已经在其自身之前被提取,因此 dist[y] > dist[x] 因为否则y 会在 x 之前被提取。 (line 13 最小距离优先)
正如我们已经假设边缘权重是正的,即length(x,y)>0。所以通过 y 的替代距离 (alt) 总是肯定会更大,即 dist[y] + length(x,y)> dist[x]。因此,即使 y 被认为是到 x 的路径,dist[x] 的值也不会被更新,因此我们得出结论:仅考虑仍在 Q 中的 y 的邻居是有意义的(注意line16 中的评论)
但这取决于我们对正边长度的假设,如果 length(u,v) 则取决于该边的负值程度,我们可能会替换 dist[x] 在line18 比较之后。
因此,如果在所有顶点 v 之前删除 x,我们所做的任何 dist[x] 计算都是不正确的 - 这样 x 是 v 的邻居,负边连接它们 - 被移除。
因为这些 v 顶点中的每一个都是从源到 x 的潜在“更好”路径上的倒数第二个顶点,这被 Dijkstra 算法丢弃。
所以在我上面给出的例子中,错误是因为 C 在 B 被删除之前被删除了。而那个 C 是 B 的邻居,有一个负边缘!
澄清一下,B 和 C 是 A 的邻居。 B有一个邻居C,C没有邻居。 length(a,b) 是顶点 a 和 b 之间的边长。
【讨论】:
Dijkstra 的算法假设路径只能变得“更重”,因此,如果您有一条从 A 到 B 的路径的权重为 3,而从 A 到 C 的路径的权重为 3,则您无法添加一条边,通过C从A到B,权重小于3。
这种假设使算法比必须考虑负权重的算法更快。
【讨论】:
Dijkstra 算法的正确性:
在算法的任何步骤中,我们都有 2 组顶点。集合 A 由我们计算到的最短路径的顶点组成。集合 B 由剩余的顶点组成。
归纳假设:在每一步,我们都会假设所有之前的迭代都是正确的。
归纳步骤:当我们在集合 A 中添加一个顶点 V 并设置距离为 dist[V] 时,我们必须证明这个距离是最优的。如果这不是最优的,那么必须有一些其他路径到顶点 V 的长度更短。
假设这条其他路径经过某个顶点 X。
现在,由于 dist[V]
因此,要使 dijkstra 算法起作用,边权重必须为非负数。
【讨论】:
在下图中尝试 Dijkstra 算法,假设 A 是源节点,D 是目标,看看发生了什么:
请注意,您必须严格遵循算法定义,而不应遵循直觉(直觉告诉您上面的路径较短)。
这里的主要观点是,该算法只查看所有直接连接的边,并采用这些边中最小的一条。该算法不向前看。您可以修改此行为,但它不再是 Dijkstra 算法了。
【讨论】:
A->B 将1 和A->C 将100。然后B->D 将2。然后C->D 将-4900。所以D 的值将是-4900 与bellman-ford 相同。这怎么没有给出正确的答案?
A->B 将是1,A->C 将是100。然后探索B 并将B->D 设置为2。那么 D 被探索是因为目前它有返回源的最短路径吗?如果B->D 是100,我会正确地说C 会首先被探索吗?我理解人们给出的所有其他例子,除了你的。
Dijkstra 的算法假设所有边都是正加权的,这个假设有助于算法运行得更快 (O(E*log(V)) 比其他考虑到的负边缘的可能性(例如复杂度为 O(V^3) 的贝尔曼福特算法)。
该算法在以下情况(带有 -ve 边)中不会给出正确的结果,其中 A 是源顶点:
这里,从源 A 到顶点 D 的最短距离应该是 6。但根据 Dijkstra 的方法,最短距离应该是 7,这是不正确的。
此外,Dijkstra 算法有时可能会给出正确的解决方案,即使存在负边。下面是这种情况的一个例子:
但是,它永远不会检测到负循环,并且总是会产生一个结果,它总是不正确如果负权重循环可以从源到达,因为在这种情况下不存在最短路径来自源顶点的图。
【讨论】:
回想一下,在 Dijkstra 的算法中,一旦一个顶点被标记为“关闭”(并且在开放集之外)-它假设任何源自它的节点都会导致更大的距离,所以,算法找到了它的最短路径,并且永远不必再次开发这个节点,但是在负权重的情况下这并不成立。
【讨论】:
您可以在不包括负循环的负边上使用 dijkstra 算法,但您必须允许一个顶点可以被多次访问,并且该版本将失去它的快速时间复杂度。
在这种情况下,实际上我发现最好使用SPFA algorithm,它有正常的队列并且可以处理负边缘。
【讨论】:
到目前为止,其他答案很好地证明了为什么 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 实际上不会循环,因为它保留了它访问过的节点列表。但它不会找到完美的路径,而是随便找一条路径。
【讨论】:
在前面的答案之上,为以下简单示例添加几点说明,
【讨论】: