前言
本章节,我们考虑“如何找到一个图中所有结点之间的最短路径”问题。我们通过运行|V|次上章节的单源最短路径算法解决所有结点对之间的最短路径问题,只需要每一次使用一个不同的结点作为源结点即可。显示复杂度有点高。本章要讨论的“更好”的算法,都是邻接矩阵来表示图。下面给出邻接矩阵表示的一些约定:
- 假定输入图G=(V,E)有n个结点,因此n=|V|。
- 使用大写字母来表示矩阵,如W、L或D,而用带下标的小写字母来表示矩阵中的元素,如wij、lij或dij。
- 一些矩阵将有带括号的上标,如 L ( m ) = ( l i j ( m ) ) L^{(m)}=(l_{ij}^{(m)}) L(m)=(lij(m))或 D ( m ) = ( d i j ( m ) ) D^{(m)}=(d_{ij}^{(m)}) D(m)=(dij(m))用来表示迭代。
- 对于一个给定的n×n矩阵A,假定矩阵的维度n存储在属性A.rows中。
最短路径和矩阵乘法
本节讨论有向图G=(V,E)上所有结点对最短路径问题的一种动态规划算法。在动态规划的每个大循环里,将调用一个与矩阵乘法非常相似的操作,因此该算法看上去就像是重复的矩阵乘法。设计动态规划算法的步骤如下:
- 分析最优解的结构。
- 递归定义最优解的值。
- 自底向上计算最优解的值。
最短路径的结构
- 假定用邻接矩阵来表示输入图,即W=(wij)。
- 从结点i到结点j的一条最短路径p,假定p至多包含m条边,且m为有限值
- 还假定没有权重为负值的环路。
- 如果i=j,则p的权重为0且不包含任何边。
- 如果结点i和结点j不同,则将路径p分解为: i → p 1 k → j i \xrightarrow[]{p^1} k\xrightarrow[]{}j ip1kj其中路径p’至多包含m-1条边。
可知p’是从结点i到结点k的一条最短路径,因此
δ
(
i
,
j
)
=
δ
(
i
,
k
)
+
w
k
j
δ(i,j)=δ(i,k)+w _{kj}
δ(i,j)=δ(i,k)+wkj。
所有结点对最短路径问题的递归解
现在设
l
i
j
(
m
)
l_{ij}^{(m)}
lij(m)为从结点i到结点j的至多包含m条边的任意路径中的最小权重.当m>0,因此递归定义:
l
i
j
(
m
)
=
min
1
≤
k
≤
n
(
l
i
k
(
m
−
1
)
+
w
k
j
)
l_{ij}^{(m)} = \min_{1\le k \le n}(l_{ik}^{(m-1)} + w_{kj})
lij(m)=1≤k≤nmin(lik(m−1)+wkj)
自底向上计算最路径权重
将上述递归式,转为算法,给定W, L作为L(m-1),L’作为L(m)。
EXTEND-SHORTEST-PATHS(L,W)
n=L.rows
let L'=(l'[i][j]) be a new n×n matrix
for i=1 to n
for j=1 to n
l'[i][j]=∞
for k=1 to n
l'[i][j]=min(l'[i][j],l[i][k]+w[k][j])
return L'
即通上述程序依次计算出矩阵序列L(1)=W,L(2),…,L(n-1)。因此总的算法有3层嵌套的for循环,该算法的运行时间为Θ(n3)。
该算法与矩阵乘法的关系(略)
Floyd-Warshall算法
本节将使用一种不同的动态规划公式来解决所有结点对最短路径问题,所产生的算法称为Floyd-Warshall算法,其运行时间为Θ(V3)。
最短路径结构
假定图G的所有结点为V={1,2,…,n},考虑其中的一个子集{1,2,…,k},这里k是某个小于n的整数。对于任意结点对i,j∈V,考虑从结点i到结点j的所有中间结点均取自集合{1,2,…,k}的路径,并且设p为其中权重最小的路径(路径p是简单路径)。
Floyd-Warshall算法利用了路径p和从i到j之间中间结点均取自集合{1,2,…,k-1}的最短路径之间的关系。该关系依赖于结点k是否是路径p上的一个中间结点:
所有结点对最短问题的一个递归解
设
d
i
j
(
k
)
d_{ij}^{(k)}
dij(k)为从结点i到结点j的所有中间结点全部取自集合{1,2,…,k}的一条最短路径的权重。当k=0时,从结点i到结点j的一条不包括编号大于0的中间结点的路径将没有任何中间结点,这样的路径最多只有一条边,因此
d
i
j
(
0
)
=
w
i
j
d_{ij}^{(0)}=w_{ij}
dij(0)=wij。根据上面的讨论,递归定义
d
i
j
(
k
)
d_{ij}^{(k)}
dij(k)如下:
d
i
j
(
k
)
=
{
w
i
j
if
k
=
0
min
(
d
i
j
(
k
−
1
)
,
d
i
k
(
k
−
1
)
+
d
k
j
(
k
−
1
)
)
if
k
≥
0
d_{ij}^{(k)} = \begin{cases} w_{ij} &\text{if } k=0 \\ \min(d_{ij}^{(k-1)},d_{ik}^{(k-1)} + d_{kj}^{(k-1)}) &\text{if } k \ge 0 \end{cases}
dij(k)={wijmin(dij(k−1),dik(k−1)+dkj(k−1))if k=0if k≥0
自底向上计算最短路径权重
根据上图的递归解,以W为输入
以下图为例
其过程如下
- D矩阵用于存储权值
- 前驱矩阵Ⅱ用于存储得到这个权值的前驱节点
- 从D矩阵可看出,4节点到5节点最小权值为-2
- 前驱矩阵Ⅱ, Ⅱ[4][5] 表示 5节点 <- 1节点, 再看 Ⅱ[4][1] 1节点 <- 4节点
- 得 4节点到5节最短路径为 4节点->1节点->5节点
用于稀疏图的Johnson算法
Johnson 算法是用来解决在有负权重边图里的最短路径问题的,它主要了结合 Dijkstra 算法和 Bellman-Ford 算法。其算法复杂度为O(V2·lgV+V·E)。
如上图,其中 0 -> 1 是负数的,是不能使用 Dijkstra 去求最短路径的。
这时我们可能会想到把全部的边都加上 5 那大家不就都变成正数了?使用 Dijkstra 求完最短路径后再减回 5 那答案不就求到了么?
上图,S->T有两条路径
- s-a-b-t 最短路径为3
- s-t 路径为4
如果用给边加上值后再减去值的方式(加1),显示最短路径会变成s-t. 因此是种不正确的方式。
Johnson算法中是通过 Bellman-Ford 的方法是给每个节点设置一个值,用这些节点的值去做 reweight。
如图添加一个虚拟的节点4,到每个结点的距离是权值0. 使用Bellman-ford 去求这个虚拟节点到每个节点的最短距离
节点 | h[x] |
---|---|
0 | 0 |
1 | -5 |
2 | -1 |
3 | 0 |
有了这些 h[x] 值后就可以对每条边进行 reweight 操作了,最终的路径应该要加上开始节点
v
0
v_0
v0的 h 值,再减去结束节点的 h 值。
w
(
p
)
=
w
(
p
)
+
h
(
v
0
)
−
h
(
v
k
)
w(p) = w(p) + h(v_0) - h(v_k)
w(p)=w(p)+h(v0)−h(vk)
Johnson 算法步骤
- 添加虚拟节点到这个图里,并添加指向所有节点的虚拟边,这些边的权重为 0
- 以虚拟节点节点为起点,运行 Bellman-Ford 算法,求出到每个节点的最短距离,这些最短距离为每个节点的 h值
- 用上面求出的 h 值去更新图里的边,使得 w ( p ) = w ( p ) + h ( v 0 ) − h ( v k ) w(p) = w(p) + h(v_0) - h(v_k) w(p)=w(p)+h(v0)−h(vk)
- 移除添加的虚拟节点和边
- 在每个节点运行 Dijkstra 计算到其它节点的最短距离
主要参考
《算法导论-上课笔记12:所有结点对的最短路径问题》
《算法: Johnson 算法》
《All-Pairs Shortest Paths》