算法导论21 Problems 答案

21-1

a

题目21.1-8给出,对于图\(G\)中的每棵最小生成树\(T\),其边权有序列表总是唯一的。由于本题中已经认定原图\(G\)的边权互不相同,因此求出一个最小生成树\(T\)后,对应构造出的列表\(L\)是唯一的,因此最小生成树是唯一的。

令图\(G\)的权值邻接矩阵如下:

\(\begin{array}{|l|l|l|l|l|}\hline &a&b&c&d\\\hline a&-&1&3&5\\\hline b&1&-&2&4\\\hline c&3&2&-&-\\\hline d&5&4&-&-\\\hline \end{array}\)

可以发现,边集\(\{(a,b),(b,c),(b,d)\}\)是最小生成树的边集,边权总和为\(7\),而\(\{(a,b),(b,c),(a,d)\}\)\(\{(a,b),(a,c),(b,d)\}\)是两棵不同的次小生成树,边权总和为\(8\)。如下图所示:

b

对于\((u,v)\in E_T\),令从\(T=(V,E_T)\)删除后连通的两部分边集\(V_1,V_2\)。对于切割\((V_1,V_2)\),由于\((u,v)\)是轻边,那么说明将横跨这个切割的边\((u,v)\)添加到\(E_T-\{(u,v)\}\)是安全的。对于其它边都是不安全的,选择一条不安全的边添加到\(E_{T}-\{(u,v)\}\)中也构成了一个树结构。假设在这些边中,边权最小的是\((a,b)\),那么\((E_T-\{(a,b)\})\cup\{(a,b)\}\)就是一颗备选的最小生成树(其它边构成的新树都不可能是次小生成树,因为它们比\((a,b)\)边权更大,更不安全)。

c

我们可以使用动态规划的过程以\(O(V)\)的时间复杂度计算\(\max[u,\cdot]\)的所有值。由于树中没有环,并不会产生循环依赖,因此我们可以写出计算\(\max[u,v]\)的状态转移方程:

\(\max[u,v]= \left \{\begin{aligned} &0 & & \text{if}\quad u=v \\ &\max\{\max[u,x],w(x,v)\} & & \text{if}\quad u\neq v \\ \end{aligned}\right.\)

其中节点\(x\)\(T\)上从\(u\)\(v\)这条路径上的倒数第二个节点。也就是说,\(\max[u,v]\)要么是从\(u\)\(x\)这条路径上的边权最大值,要么是\((x,v)\)的边权。因此,通过对这棵树进行BFS,最终能够以\(O(V)\)的算法计算出所有\(\max[u,\cdot]\)的值,这个算法由程序BFS-DIS给出。

最终,枚举所有起点\(s\),我们可以以\(O(V^2)\)的时间计算出所有\(\max[\cdot,\cdot]\)的值,其通过程序CAL-DIS给出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
BFS-DIS(T, s, w)
for each vertex u ∈ T.V - {s}
u.d = ∞
u.π = NIL
s.d = 0
s.π = NIL
Q = ∅
ENQUEUE(Q, s)
while Q != ∅
u = DEQUEUE(Q)
for each vertex v in T.Adj[u] // search the neighbors of u
if v.d == ∞ // is v being discovered now?
v.d = u.d + w(u, v)
v.π = u
ENQUEUE(Q, v) // v is now on the frontier

CAL-DIS(T, w)
let max be new 2-dimension array
for each vertex u ∈ T.V
BFS-DIS(T, u, w)
for each vertex v ∈ T.V
max[u, v] = v.d
return max

d

通过题目21-1-c给出的CAL-DIS算法,我们可以给出求次小生成树的算法。其思想是,枚举所有不属于最小生成树\(T=(C,E_T)\)的边\((u,v)\),并尝试将其添加到\(T\)中,那么树\(T\)和边\((u,v)\)就形成了一个环\(C\),因此需要在\(C\)中删除一条除\((u,v)\)以外的边权最大的边,以使得增加的边权值最小(虽然是严格增加了)。整个算法由程序2ND-MST给出,其关键瓶颈在于计算\(\max[\cdot,\cdot]\),整个算法的时间复杂度为\(O(V^2)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2ND-MST(G, w)
// 首先求出一棵最小生成树
T.V, T.E = G.V, MST-KRUSKAL(G)
max = CAL-DIS(T, w)
// 次小生成树比最小生成树多出的边权总和值。
new-dif = ∞
create a single list of the edges in G.E
for each edge (u, v) taken from the list
if (u, v) not in T.E
dif = w(u, v) - max[u, v]
if dif < new-dif
(mu, mv) = (u, v)
new-dif = dif
remove the edge e from T.E s.t. e is in path(mu, mv) and w(e) is maximum
INSERT(T.E, (mu, mv))
return T

接下来给出一种基于LCA(最近公共祖先)倍增的做法。首先需要以\(O(V\lg V)\)的时间对\(T\)预处理出两个表(这里需要对\(T\)指定一个根):\(fa[u,i]\)表示\(u\)的第\(2^i\)个祖先,\(m[u,i]\)表示\(\max[u,fa[u,2^i]]\)的值。

那么接下来对于所有边\((u,v)\in E-E_T\),以\(O(\log V)\)的时间在表\(fa\)上计算它们的LCA:\(l\),并且计算出\(\max[u,l]\)\(\max[v,l]\)的值,最终以\(O(\lg V)\)的时间计算出\(\max[u,v]\)的值。因此这个算法可以以\(O(E\lg V)\)的时间复杂度求出次小生成树。

21-2

a

也就是说,证明由算法MST-REDUCEMST-PRIM联合所生成的边集是最小生成树的边。

对于算法MST-REDUCE生成的边集\(T\),假设当前第4行代码正在访问的节点是\(u\),那么考虑切割\((\{u\},V-\{u\})\),第6行所选定的边是横跨切割\((\{u\},V-\{u\})\)并且是一条轻量级边,因此是安全的。此外将这两个节点通过这条边收缩起来,以后它们统一视为一个节点。

算法MST-PRIM接受输入的是来自MST-REDUCE的输出图\(G'\)。由于所有树边已经被收缩成一个节点,因此仍未收缩的边将会连通任意一个不同代表节点。对这些剩余边进行最小生成树算法将会得到剩下的安全边。因此$ T {(x, y).orig′ : (x, y) ∈ A}\(是\)G$的一棵最小生成树。

b

首先可以发现\(|G'.V|+|T|=|V|\)。因为\(T\)中每产生一条边,\(G\)中就会有\(2\)个节点被合并成一个,也就是说,节点数降低了\(1\)

当第5行代码if条件成功进入时,第10行代码有可能会另一个端点\(v\)从未标记变成已标记,这意味着以后并不能再产生一条边加入\(T\)中。因此有\(|T|\ge |V|-|T|\),也就是说,有\(|T|\ge |V|/2\)。因此得到\(|G'.V|\le |V|/2\)

c

使用一个长度为\(|V|\)的数组\(A\)可以代替并查集操作。花费时间\(O(V)\)对数组\(A\)初始化成A[u] = u,对于所有\(V\)中的节点\(u\)FIND-SET(u)可以使用A[u]替代,UNION(u, v)可以用A[v] = A[u]替代(注意不可以反过来)。这种做法成立的原因是,可以直接访问当前连通块中的代表元素。最终,算法MST-REDUCE可以\(O(E)\)的时间完成。

d

每一次调用则意味着对这个图进行一次\(O(E)\)的操作,虽然节点数至少减少了一半,但是边数减少的数量也仅仅至少是节点数的减少量。因此\(k\)次调用意味着\(O(kE)\)的时间复杂度。

e

进行\(k\)次调用后,那么整个图的节点数最多为\(\dfrac{|V|}{2^k}\)。因此我们需要选择\(k\)来使得\(|E|+\dfrac{|V|}{2^k}\lg\dfrac{|V|}{2^k}+kE=O(|E| \lg\lg |V|)\)。题目给定的因子\(\lg\lg B\)提示我们考虑验证取值\(k=\lg\lg |V|\)是成立的。那么有

\(\begin{aligned} |E|+\dfrac{|V|}{2^k}\lg\dfrac{|V|}{2^k}+kE&=|E|+\dfrac{|V|}{\lg |V|}\lg\dfrac{|V|}{\lg |V|}+|E|\lg\lg |V|\\ &=\dfrac{|V|}{\lg |V|}\lg\dfrac{|V|}{\lg |V|}+O(|E|\lg\lg |V|)\\ &=\dfrac{|V|}{\lg |V|}(\lg |V|-\lg\lg |V|)+O(|E|\lg\lg |V|)\\ &=|V|-\dfrac{|V|\lg\lg |V|}{\lg |V|}+O(|E|\lg\lg |V|)\\ &=|V|+O(|E|\lg\lg |V|)\\ &=O(|E|\lg\lg |V|) \end{aligned}\)

f

如果这种带预处理的操作优于没有带预处理的操作,那么有\(|E|\lg\lg |V|<|E|+|V|\lg |V|\),也就是得到\(|E|<\dfrac{|V|\lg |V|}{\lg\lg |V|-1}\)。因此最终有\(|E|=O\left(\dfrac{|V|\lg |V|}{\lg\lg |V|}\right)\)

21-3

A

算法MAYBE-MST-A返回的是一个最小生成树。当我们按边权从大到小访问这些边\((u,v)\)时,考虑两种情况:

  1. \((u,v)\)没有被删去。这说明如果\((u,v)\)被删去了,这个图就不连通了,为保证\(T\)中的边连通,\((u,v)\)必须留下。
  2. \((u,v)\)成功被删去了。令\((V_1,V_2)\)是被边\((u,v)\)横跨的一个切割。由于删去\((u,v)\)后,\(T\)仍然连通,因此在这个时候,仍然有从\(u\)\(v\)的一条路径,也就是说存在其它边\((a,b)\)仍然横跨\((V_1,V_2)\)。这条边仍然是未被迭代的,因此\((a,b)\)在切割\((V_1,V_2)\)中至少比\((u,v)\)安全。因此可以删去这条边。

算法MAYBE-MST-A的实现依赖于判断图\(T\)的连通性,第4行的伪代码直接进行判断。逆序遍历每一条边然后删除边后判断\(T\)的连通性,这一次判断过程需要\(O(V+E)\)的运行时间。因此整个算法需要\(O(E^2)\)的运行时间。

B

算法MAYBE-MST-B返回的不是一个最小生成树。令\(G=(V,E),V=\{a,b,c\},E=\{(a,b),(b,c),(a,c)\},w(a,b)=1,w(a,c)=2,w(b,c)=3\)。如下图所示:

如果边\((b,c)\)先被访问,那么它们就被添加到了树边集合\(T\)中,构成的\(T\)永远不会是最小生成树。因此算法MAYBE-MST-B是错误的。

算法MAYBE-MST-B的实现依赖于并查集,只需要和Kruskal算法一样,边判断两个端点是否属于同一集合边合并即可。程序MAYBE-MST-B的具体实现由程序MAYBE-MST-B'给出,其时间复杂度为\(O(E)\)

1
2
3
4
5
6
7
8
9
10
MAYBE-MST-B'(G, w)
A = Ø
for each vertex v ∈ G.V
MAKE-SET(v)
create a single list of the edges in G.E
for each edge (u, v) taken from the list
if FIND-SET(u) ≠ FIND-SET(v)
A = A ∪ {(u, v)}
UNION(u, v)
return A

C

算法MAYBE-MST-C返回的是一个最小生成树。当我们按任意顺序访问并添加这些边\((u,v)\)\(T\)后,考虑两种情况:

  1. \(T\)中没有环,这说明\((u,v)\)是所必须要的边。
  2. \(T\)中产生了环\(C\),令\((a,b)\)\(c\)中最大边权的那条边。令\((V_1,V_2)\)是被边\((a,b)\)横跨的一个切割,那么可以知道,环\(C\)上有另外一条边\((x,y)\)横跨切割\((V_1,V_2)\)。由于\((a,b)\)的边权是最大的,因此有\(w(x,y)\le w(a,b)\)。也就是说,\((x,y)\)在这个切割中肯定比\((a,b)\)安全,因此可以删去\((a,b)\)这条边。

算法MAYBE-MST-C的实现依赖于求出\(C\)中从\(u\)\(v\)的这条路径。程序MAYBE-MST-C的具体实现由程序MAYBE-MST-C'给出,每次进行求解路径的时间复杂度为\(O(V)\)。因此整个算法需要\(O(VE)\)的运行时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MAYBE-MST-C'(G, w)
A = Ø
for each vertex v ∈ G.V
MAKE-SET(v)
create a single list of the edges in G.E
for each edge (u, v) taken from the list
A = A ∪ {(u, v)}
if FIND-SET(u) ≠ FIND-SET(v)
UNION(u, v)
else:
get the cycle C where edge (u, v) in
select the edge e from C s.t. w(e) is maximum
A = A - {e}
return A

21-4

a

\(T\)\(G\)中的最小生成树,\(T'\)\(G\)中的一棵瓶颈生成树。假设\(T\)不是瓶颈生成树,也就是\(T\)中存在一条边\((u,v)\),使得\(w(u,v)>m\),其中\(m\)是瓶颈生成树的最大边权值。那么令\(T_1,T_2\)是从\(T\)删去\((u,v)\)后形成的两棵树,\(V_1,V_2\)分别表示这两棵树的节点集合。考虑切割\((V_1,V_2)\),由于\(T'\)是连通的,因此必定存在一条边\((a,b)\)横跨\((V_1,V_2)\)。由于\(w(a,b)\le m<w(u,v)\),因此边\((a,b)\)比边\((u,v)\)更安全,也就是说,从\(T\)删去\((u,v)\)并加上\((a,b)\)后,将会得到一棵更优的最小生成树,这是矛盾的。因此最小生成树是瓶颈生成树。

b

只要一个图\(G\)是连通的,那么我们总能找到一棵生成树。因此该问题的本质上是判断图\(G\)中小于等于\(b\)的所有边是否构成\(G\)中的一个连通图。我们通过改造BFS算法即可以以\(O(V+E)\)的时间复杂度判断\(b\)值是否可行。这个算法由程序CHECK-NECKBOTTLE-BOUND给出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CHECK-NECKBOTTLE-BOUND(G, w, b)
for each vertex u ∈ G.V
u.d = ∞
u.π = NIL
select s ∈ G.V randomly
s.d = 0
s.π = NIL
Q = ∅
ENQUEUE(Q, s)
while Q != ∅
u = DEQUEUE(Q)
for each vertex v in G.Adj[u]
if v.d == ∞ and w(u, v) <= b
v.d = u.d + 1
v.π = u
ENQUEUE(Q, v)

c

我们将使用子程序BST-REDUCE对所有满足\(w(u,v)\le b\)的边进行收缩,整个过程的时间复杂度为\(O(E)\),因为它遍历了两次所有边,第一次遍历是将所有满足\(w(u,v)\le b\)中的边收缩成点,第二次遍历则是将满足\(w(u,v)> b\)映射到新图中的点。

主程序由GEN-NECKBOTTLE-TREE给出,它使用了第9章的算法来求中位数,并划分所有边。可以发现,第6行的while循环迭代一轮后,图\(G\)中的边数至少减少一半。而while循环后面的补边操作全过程也是\(O(E)\),因此这个算法的时间复杂度为\(O(E)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
BST-REDUCE(G, T, b)
for each vertex v ∈ G.V
MAKE-SET(v)
for each (u, v) in G.E
// 对小于等于门限b的边进行收缩。
if (u, v).c <= b and FIND-SET(u) != FIND-SET(v)
UNION(u, v)
T = T ∪ {(u, v).orig}
G'.V = {FIND-SET(v) : v ∈ G.V}
G'.E = Ø
for each (x, y) in G.E
u = FIND-SET(x)
v = FIND-SET(y)
// 对大于门限b的边两侧的节点进行置换。
if (x, y).c > b and u != v:
if (u, v) ∉ G'.E
G'.E = G'.E ∪ {(u, v)}
(u, v).orig' = (x, y).orig
(u, v).c' = (x, y).c
else if (x, y).c < (u, v).c'
(u, v).orig' = (x, y).orig
(u, v).c' = (x, y).c
for each (u, v) in G'.E
(u, v).orig = (x, y).orig'
(u, v).c = (x, y).c'
return (G', T)

GEN-NECKBOTTLE-TREE(G', w)
T = Ø
let G be a copy of G'
for each (u, v) in G.E
(u, v).orig = (u, v)
(u, v).c = w(u, v)
while G.E != Ø
find the median of w(e) for e in G.E and get the median b
if not CHECK-NECKBOTTLE-BOUND(G, w, b)
G, T = BST-REDUCE(G, T, b)
else
remove the edges e from G.E s.t. w(e) >= b
// 需要注意的是,这里的T还不是瓶颈生成树,因为它去掉的边包含了临界值m,现在需要对T再补上权值恰好为b的边。
let m be the median we last determined
for each vertex v ∈ G.V
MAKE-SET(v)
for each (u, v) in T
UNION(u, v)
for each (u, v) in G.E
if w(u, v) == m and FIND-SET(u) ≠ FIND-SET(v)
T = T ∪ {(u, v)}
UNION(u, v)
return T