算法导论 23.2 Exercises 答案

23.2-1

每步的迭代结果如下:

(01102208403705100)k=1(011020208405705100)k=2(01102032042840587906510750)k=3(01102032042840587906510750)k=4(01202302041840557903510720)k=5(06812023020418420557903510720)k=6(06812023530168420557903510720)

23.2-2

对于图 G=(V,E) 的邻接矩阵 W,我们设 wij=1 当且仅当边 (i,j)E,此外,iV,wii=1。接下来参照子程序 EXTEND-SHORTEST-PATHS,构造出子程序 EXTEND-PATHS'。那么剩下的是调用这个子程序即可求出传递闭包,整个过程和 23.1 节一致,由 TRANSITIVE-CLOSURE' 给出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
EXTEND-PATHS'(T(r-1), W, T(r), n)
for i = 1 to n
for j = 1 to n
for k = 1 to n
t_{ij}^(r) = t_{ij}^(r) ∨ (t_{ik}^(r-1) ∧ w_{kj}^(r-1))

TRANSITIVE-CLOSURE'(W, n)
let T and M be new n × n matrices
T = W
r = 1
while r < n - 1
M = 0
EXTEND-PATHS''(T, T, M, n)
r = 2r
T = M
return T

23.2-3

按照方程 23.7 和 23.8,修改后的算法为 FLOYD-WARSHALL''

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FLOYD-WARSHALL''(W, n)
D^(0) = W
let Π^(0) be a new n × n matrix
for i = 1 to n
for j = 1 to n
if w_{ij} < ∞ and i != j
π^(0)_{ij} = i
else
π^(0)_{ij} = NIL
for k = 1 to n
let D^(k) = (d^(k)_{ij}),Π^(k) = (π^(k)_{ij}) be new n × n matrices
for i = 1 to n
for j = 1 to n
if d^(k-1)_{ij} > d^(k-1)_{ik} + d^(k-1)_{kj}
d^(k)_{ij} = d^(k-1)_{ik} + d^(k-1)_{kj}
π^(k)_{ij} = π^(k-1)_{kj}
else
d^(k)_{ij} = d^(k-1)_{ij}
π^(k)_{ij} = π^(k-1)_{ij}
return D^(n), Π^(n)

首先证明 Gπ,i 是一个有向无环图。当 πij(k) 被更新成一个值 l 时,说明 dij(k)=dil(k1)+wlj。由于 dij(k1) dij(k) 的一个子问题选项,因此有 dij(k)dij(k1),从而得到 dij(k)dil(k1)+wlj。接下来的证明和引理 22.16 的证明类似。假设 Gπ,i 构成了一个环 v0,v1,,vm,其中 v0=vm,j[1,m],πi,vj(k)=vj1 都成立。那么在 πi,vm 更新成 vm1 前,都有:

di,vj(k)di,vj1(k)+wvj1,vj,j=1,2,,m1

由于 πi,vm 被更新,所以在 πi,vm 被更新成 vm1 前,有

di,vm(k)>di,vm1(k)+wvm1,vm

将这 m 条不等式左右各自相加,最终得到 j=1mw(vj1,vj)<0。这和图 G 没有负环是矛盾的。因此,Gπ,i 是一个有向无环图。

接下来和引理 22.17 的证明类似。Gπ,i i 到每个节点 u 之间只存在唯一一条路径。如果存在两条路径 p1:iuxzu,p2:iuyzu,并且 xy,那么说明 πiz=x,πiz=y 同时成立。这是不可能的,因此从 i 到每个节点 u 之间只存在唯一一条路径,Gπ,i 是以 i 为根形成的一棵树。

最终,由于算法正确地求解出了 dij(n)=δ(i,j),因此对于所有顶点 i,Gi 都是以 i 为根的最短路径树。

23.2-4

可以知道,这个对 dij(k) 的更新本质上是来自 {dik(k)+dkj(k),dik(k1)+dkj(k1),dik(k)+dkj(k1),dik(k1)+dkj(k)} 4 组值中的某一个,我们将通过证明 dik(k1)=dik(k),dkj(k1)=dkj(k),从而说明这个更新是正确的。

考虑先证明 dik(k1)=dik(k)。根据 dij(k) 的定义:从 i j 的路径中,中间节点只包含节点 1,2,,k 的最短路径。由于 k 是这条最短路径中的一个端点k 不在 dik(k1) 中,如果再尝试多途经一个节点 k,那么来自路径 dik(k) 的最短路径必定不会劣于 dik(k1)(因为 dik(k1) dik(k) 的一个子问题)。因此有 dik(k1)dik(k)。如果 k 是从 i k 这条路径的一个中间节点,那么路径上就会形成环,因此有 dik(k1)dik(k)。从而得出 dik(k1)=dik(k)

可以用类似的证明方式,得到 dkj(k1)=dkj(k)。最终可知,这种更新方式是正确的。

23.2-5

由这个方式构造出来的矩阵是正确的。

此处仅讨论 dij(k1)=dik(k1)+dkj(k1) 时的这种情况,因为如果等号不成立,情况和方程 23.8 时的一致。当第 k 轮循环结束时,保证有 dij(k)=di,πij(k1)+dπij,j(k1)

本题对应的情况是 πij(k)=πkj(k1)。由于 dij(k1)=dij(k)=dik(k1)+dkj(k1),因此 k 也可以作为从 i j 某条最短路径中的一个节点,由于 dkj(k1) 也是取自某条从 k j 最短路径的长度,作为一条子路径,它的前驱 πkj(k1) 也适用于 πij(k)

23.2-6

如果其最终输出 D(n) 的主对角线上存在负数,那么说明 G 中存在负环。因为某次 dikk1+dkik1<0 的更新导致了主对角线上 diik 的值小于 0

23.2-7

由于 Floyd-Warshall 算法是从小到大插入中间节点 k 来求解最短路径的,因此如果节点 k 确实对从 i j 的路径有实在的贡献,那么 ϕij(k)=k,否则有 ϕij(k)=ϕij(k1),也就是沿用上一次迭代的结果。更正式地可以将 Φ(k) 写成:

ϕij(k)={kifdij(k1)>dik(k1)+dkj(k1)ϕij(k1)ifdij(k1)dik(k1)+dkj(k1)

那么可以将 Floyd-Warshall 算法进行改写以求出 Φ,由 FLOYD-WARSHALL''' 给出:

1
2
3
4
5
6
7
8
9
10
11
12
13
FLOYD-WARSHALL'''(W, n)
D^(0) = W
for k = 1 to n
let D^(k) = (d^(k)_{ij}),Φ^(k) = (ϕ^(k)_{ij}) be new n × n matrices
for i = 1 to n
for j = 1 to n
if d^(k-1)_{ij} > d^(k-1)_{ik} + d^(k-1)_{kj}
d^(k)_{ij} = d^(k-1)_{ik} + d^(k-1)_{kj}
ϕ^(k)_{ij} = k
else
d^(k)_{ij} = d^(k-1)_{ij}
d^(k)_{ij} = ϕ^(k-1)_{ij}
return D^(n), Φ^(n)

修改后的 PRINT-ALL-PAIRS-SHORTEST-PATH 由程序 PRINT-ALL-PAIRS-SHORTEST-PATH' 给出,它需要调用一个子程序 AUX-PRINT-PATH 来保证路径能够被正确遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 这个函数用来打印除了起点s和终点t以外的所有点。
AUX-PRINT-PATH(Φ, W, n, s, t)
if ϕ_{st} == NIL
return
x = ϕ_{st}
AUX-PRINT-PATH(Φ, W, n, s, x)
print x
AUX-PRINT-PATH(Φ, W, n, x, t)

PRINT-ALL-PAIRS-SHORTEST-PATH(Φ, W, n, s, t)
if ϕ_{st} == NIL and w_{st} == ∞
print "no path from" i "to" j "exists"
else if s == t
print s
else
print s
AUX-PRINT-PATH(Φ, W, n, s, t)
print t

Φ 和矩阵链乘所得到的表 s 是一样的,它们表示的内容都是当前最优情况下,对对路径 / 乘法阶段的最优划分。然而,Φ 表的路径追踪比 s 表难,因为 Φ 表中的元素并不能直接获得特定的前驱或者是后继节点,只能向下一步步遍历。

23.2-8

枚举 G=(V,E) 中的每一个节点 s 进行 BFS 即可,被标记的节点都是能从 s 到达的节点。枚举完 V 中所有节点得到的结构恰好就是传递闭包。

整个算法由程序 TRANSITIVE-CLOSURE'' 给出。每进行一次 BFS 的时间复杂度为 O(V+E)=O(E),因此进行 |V| 次 BFS 的时间复杂度为 O(VE)

1
2
3
4
5
6
7
8
TRANSITIVE-CLOSURE''(G)
E = ∅
for each s ∈ G.V
BFS(s)
for each v ∈ G.V
if v.color == BLACK:
E = E ∪ {(s, v)}
return (G.V, E)

23.2-9

考虑以 3 个步骤计算出有向图 G=(V,E) 的传递闭包。

  1. 先花费 O(V+E) 的时间求出 G 的强连通分量,再以 O(V+E) 的时间计算出这些强连通分量的分量图 GSCC=(VSCC,ESCC)。注意到,|VSCC||V|,|ESCC||E|,并且 GSCC 是一个有向无环图。

  2. 使用题中算法所描述的算法求解 GSCC 的传递闭包 GSCC=(VSCC,ESCC),这将花费 f(|VSCC|,|ESCC|) 的时间。由于 f 是一个单调递增函数,因此说明只需要不超过 f(|V|,|E|) 的时间即可构造出 GSCC

  3. 考虑通过 GSCC 来构造 G=(V,E)。假设求解分量图完成后,强连通分量 Ci 表示图 G 中属于这个分量的所有节点,vi Ci GSCC 上对应的节点。那么首先枚举 Ci 中的所有节点,并将它们成对的边添加到 E 中,每次枚举并添加一条边只需要 O(1) 的时间。对于 (vi,vj)ESCCuvi,vvj,(u,v)E,同样的,每次枚举并添加一条边只需要 O(1) 的时间。添加完所有边恰好需要 O(V+E) 的时间。

因此,这 3 部分所需要花费的时间为 f(|V|,|E|)+O(V+E)+O(V+E),即可得到 f(|V|,|E|)+O(V+E)