Shortest Path Algorithms
Weighted Graphs
通常,我们发现沿着图中的一条边移动会有一些相关的成本(或利润),例如边的距离,汽油的价格,花费的时间等等。
我们称这之为加权图,称边值为权值。
我们将之前的图定义扩展如下:
加权图G由有序序列(V, E, W)组成,其中V和E是顶点和边,W是边的权值。
V={a, b, c, d, e}
E={(a, b), (a, c), (a, d), (a, e), (c, b), (d, b),(d, c), (e, d)}
W((a, b))=50, W((a, c))=30等等。
同时我们可以用图的邻接矩阵表示为:
“-”表示边缘不存在。
最短路径
与加权图有关的一个常见问题是寻找顶点之间的最短路径。
Dijkstra’s Algorithm
该算法的工作原理是将顶点分为两个集合,S和C。
在每个迭代;S包含已经被选择的节点集。
S也叫做选择集 selected set.
在每个迭代;C包含了一组尚未被选中的节点:
C也叫做候选集 candidate set.
在每一步中,我们将到达C的最小节点移动到S。
我们还需要一个函数D使D(ci)是我们目前找到的候选集合c中从顶点s到顶点ci的最短距离。
举个例子:
Step 0:
S={a}
C={b, c, d, e}
D()= 50, 30, 100, 10
我们现在选择D的最小值,D(e)=10。
Step 1:
我们将顶点e从C移动到S。
S={a, e}
C={b, c, d}
D()= 20, 30, 50
a到d有两条,一条为a-e-d:10+10=20,另一条是a-d=100,20<100所以我们不需要在D()中再加入100,后面出现这种情况同理。
我们现在选择D的最小值,D(d)=20。
Step 2:
将顶点d从C移动到S。
S={a, e, d}
C={b, c}
D()= 40, 30
我们现在选择D的最小值,D©=30。
Step 3:
将顶点c从c移动到S。
S={a, e, d, c}
C={b}
D()= 35 // a-c-b 30+5=35
我们现在选择D的最小值,D(b)=35
Step 4:
将顶点b从C移动到S
S={a, e, d, c, b}
C={}
C中没有候选顶点了,所以我们完成了。
结果: W(a)=0, W(e)=10, W(d)=20, W©=30, W(b)=35.
Bellman-Ford
适用于具有负边权值的图。
识别负循环和路径上有负循环的顶点。
为所有其他顶点找到正确的路径和路径长度。
举个例子:
上图中a-c-b组成了一个循环,这个循环的总权值为-1,这意味着如果按照刚才Dijkstra’s Algorithm来的话,所有路径都可以重复走这个cycle让自己权值降到最低。
因此我们如何用Bellman-Ford考虑呢?
与Dijkstra算法不同的是,Dijkstra算法在每次迭代时只更新最有希望的(下一个成本最低的)顶点,Bellman-Ford在每次迭代中更新每个顶点。
这意味着Bellman-Ford的每次迭代都比Dijkstra的迭代需要更多的工作。
拿上图举例:
它有6个顶点,所以我们将在主循环中运行5次。
初始化:
我们为D设置初始值:
D(s)=0
s | a | b | c | d | e |
---|---|---|---|---|---|
0 | ∞ | ∞ | ∞ | ∞ | ∞ |
迭代1:
顶点 s: 我们可以到达a和e, 更新。
顶点 a: 我们可以到达c, 更新。
顶点 b: 我们还不能到达,跳过。
顶点 c: 我们可以到达b, 更新。
顶点 d: 我们还不能到达,跳过。
顶点 e: 我们可以到达d, 更新。
下表为第一次迭代结束后的权值:
s | a | b | c | d | e |
---|---|---|---|---|---|
0 | 10 | 10 | 12 | 3 | 8 |
迭代2:
顶点 s: 我们可以到达a和e, 没有变化。
顶点 a: 我们可以到达c, 没有变化。
顶点 b: 我们可以到达a,更新
顶点 c: 我们可以到达b, 没有变化。
顶点 d: 我们可以到达a和c,更新。
顶点 e: 我们可以到达d, 没有变化。
下表为第二次迭代结束后的权值:
s | a | b | c | d | e |
---|---|---|---|---|---|
0 | -1 | 10 | 2 | 3 | 8 |
注意:
a现在的权值是s-e-d-a=8-5-4=-1
c现在的权值是s-e-d-c=8-5-1=2
后面同理。
迭代3:
顶点 s: 我们可以到达a和e, 没有变化。
顶点 a: 我们可以到达c, 更新。
顶点 b: 我们可以到达a,没有变化。
顶点 c: 我们可以到达b, 更新。
顶点 d: 我们可以到达a和c,没有变化。
顶点 e: 我们可以到达d, 没有变化。
下表为第三次迭代结束后的权值:
s | a | b | c | d | e |
---|---|---|---|---|---|
0 | -1 | -1 | 1 | 3 | 8 |
迭代4:
顶点 s: 我们可以到达a和e, 没有变化。
顶点 a: 我们可以到达c, 没有变化
顶点 b: 我们可以到达a,更新。
顶点 c: 我们可以到达b, 没有变化。
顶点 d: 我们可以到达a和c,没有变化。
顶点 e: 我们可以到达d, 没有变化。
下表为第四次迭代结束后的权值:
s | a | b | c | d | e |
---|---|---|---|---|---|
0 | -2 | -1 | 1 | 3 | 8 |
迭代5:
顶点 s: 我们可以到达a和e, 没有变化。
顶点 a: 我们可以到达c, 更新。
顶点 b: 我们可以到达a,没有变化。
顶点 c: 我们可以到达b, 更新。
顶点 d: 我们可以到达a和c,没有变化。
顶点 e: 我们可以到达d, 没有变化。
下表为第五次迭代结束后的权值:
s | a | b | c | d | e |
---|---|---|---|---|---|
0 | -2 | -2 | 0 | 3 | 8 |
五次迭代结束,
检查:
顶点 s: 我们可以到达a和e, 没有变化。
顶点 a: 我们可以到达c, 把a标记为bad。
顶点 b: 我们可以到达a,没有变化。
顶点 c: 我们可以到达b, 没有变化。
顶点 d: 我们可以到达a和c,没有变化。
顶点 e: 我们可以到达d, 没有变化。
s | a | b | c | d | e |
---|---|---|---|---|---|
0 | X | -2 | 0 | 3 | 8 |
结论:
我们可以得出结论,图包含一个包含顶点a的负成本循环。
如果我们从顶点a开始执行DFS(或BFS),将所有访问过的顶点标记为bad,我们得到以下结果:
s | a | b | c | d | e |
---|---|---|---|---|---|
0 | X | X | X | 3 | 8 |
因此:
顶点 a: 通过循环到达。
顶点 b: 通过循环到达。
顶点 c: 通过循环到。
顶点 d: 可到达的花费为3。
顶点 e: 可到达的花费为8。
A*算法
公式:
f(n)=g(n)+h(n),
f(n) 是从初始状态经由状态n到目标状态的代价估计。
g(n) 是在状态空间中从初始状态到状态n的实际代价。
h(n) 是从状态n到目标状态的最佳路径的估计代价。
放在路径搜索问题中,状态就是图中的节点,代价就是距离或者权重。
A*的诀窍是找到一个好的启发式。
对顶点选择规则的启发式修改将Dijkstra算法变成了一个被称为A*算法的例子。
尽管在最坏的情况下,A*并不比Dijkstra更快,但在实践中,它通常代表着实质性的改进。
从v到g的估计最小路径长度H(v, g)与实际最小路径长度P(v, g)越接近,A*就越快找到解。
References
- Introduction to The Design and Analysis of Algorithms, A. Levitin, 3rd Ed., Pearson 2011.
- Introduction to Algorithms, T. H. Cormen, 3rd Ed, MIT Press 2009.