我试图理解为什么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)
}
谢谢,
梅尔
答案 0 :(得分:186)
您建议的算法确实会在此图中找到最短路径,但不是所有图形。例如,请考虑以下图表:
假设边缘从左到右指向,如示例所示,
您的算法将按如下方式运行:
d(A)
设置为zero
,将其他距离设置为infinity
。 A
,将d(B)
设置为1
,将d(C)
设置为zero
,将d(D)
设置为99
}。C
,不做任何净更改。B
,这无效。 D
,将d(B)
更改为-201
。请注意,尽管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算法有3种版本,但该问题下的所有答案都忽略了这些变体之间的差异。
for
循环来放松顶点。这是实现Dijkstra算法的最简单方法。时间复杂度为O(V ^ 2)。版本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 算法的方法:
标记已经找到与源的最小距离的节点(更快的算法,因为我们不会重新访问已经找到最短路径的节点)
不标记已经找到离源最小距离的节点(比上面慢一点)
现在问题来了,如果我们不标记节点以便我们可以找到最短路径,包括那些包含负权重的路径会怎样?
答案很简单。考虑图中只有负权重的情况:
)
现在,如果您从节点 0(源)开始,您将有如下步骤(这里我没有标记节点):
0->0 as 0, 0->1 as inf , 0->2 as inf 开头
0->1 为 -1
0->2 为 -5
0->0 为 -8(因为我们没有放松节点)
0->1 为 -9 .. 依此类推
这个循环将永远持续下去,因此 Dijkstra 算法无法找到负权重的最小距离(考虑所有情况)。
这就是为什么 Bellman Ford Algo 用于在负权重的情况下找到最短路径的原因,因为它会在负循环的情况下停止循环。