proof of Kosaraju
该证明存在瑕疵,我只是为了方面我自己使用,所以有些符号和名词我并没有明说定义,此外在某些地方你可以将s当做v,而v当做w。
以下证明需要结合算法4的对应代码
public class KosarajuSharirSCC {
private boolean[] marked; // marked[v] = has vertex v been visited?
private int[] id; // id[v] = id of strong component containing v
private int count; // number of strongly-connected components
public KosarajuSharirSCC(Digraph G) {
// compute reverse postorder of reverse graph
DepthFirstOrder dfs = new DepthFirstOrder(G.reverse());
// run DFS on G, using reverse postorder to guide calculation
marked = new boolean[G.V()];
id = new int[G.V()];
for (int v : dfs.reversePost()) {
if (!marked[v]) {
dfs(G, v);
count++;
}
}
// check that id[] gives strong components
assert check(G);
}
// DFS on graph G
private void dfs(Digraph G, int v) {
marked[v] = true;
id[v] = count;
for (int w : G.adj(v)) {
if (!marked[w]) dfs(G, w);
}
}
public int count() {
return count;
}
public boolean stronglyConnected(int v, int w) {
validateVertex(v);
validateVertex(w);
return id[v] == id[w];
}
public int id(int v) {
validateVertex(v);
return id[v];
}
// does the id[] array contain the strongly connected components?
private boolean check(Digraph G) {
TransitiveClosure tc = new TransitiveClosure(G);
for (int v = 0; v < G.V(); v++) {
for (int w = 0; w < G.V(); w++) {
if (stronglyConnected(v, w) != (tc.reachable(v, w) && tc.reachable(w, v)))
return false;
}
}
return true;
}
// throw an IllegalArgumentException unless {@code 0 <= v < V}
private void validateVertex(int v) {
int V = marked.length;
if (v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
}
public static void main(String[] args) {
In in = new In(args[0]);
Digraph G = new Digraph(in);
KosarajuSharirSCC scc = new KosarajuSharirSCC(G);
// number of connected components
int m = scc.count();
StdOut.println(m + " strong components");
// compute list of vertices in each strong component
Queue<Integer>[] components = (Queue<Integer>[]) new Queue[m];
for (int i = 0; i < m; i++) {
components[i] = new Queue<Integer>();
}
for (int v = 0; v < G.V(); v++) {
components[scc.id(v)].enqueue(v);
}
// print results
for (int i = 0; i < m; i++) {
for (int v : components[i]) {
StdOut.print(v + " ");
}
StdOut.println();
}
}
}
命题:设 G R G^R GR是 G G G的反向图,将 G R G^R GR的逆后序遍历顺序放到dfs( G G G, G R 逆后序遍历顺序 G^R逆后序遍历顺序 GR逆后序遍历顺序)中,则构造函数中的每一次递归调用的顶点都标记了同一个SCC中。
设v为初始顶点, w为dfs递归中的adj(v),要证上述命题,实际上就是证:
w和v是强连通的,每个w都会在构造函数的dfs(G,v)中被访问到。 且构造函数中dfs(G,v)所到达的顶点w都和v强连通。
该证明的核心是,1、如何保证若v和w是强连通的,构造函数dfs(G,v)一定能访问到所有的w。2、所有被构造函数dfs(G,v)访问w一定是跟v强连通的
第一部分
第一部分的证明用反证法:若dfs(G,v)不访问w,因为v->w的关系,所以只能认为w是被其他v0或v1什么的访问过;因为w跟v强连通,所以dfs(G,w)一定能访问到v,但v已经被访问过了,出现矛盾。所以第一部分得证;
第二部分
第二部分,因为已经证明v一定能到达w,此时只需要证明w一定能到达v即可。后者等价于 v G R − > w G R v_{G^R} -> w_{G^R} vGR−>wGR。
考虑以下这个情况,对于 w G − > v G w_G ->v_G wG−>vG来说,取反图则为 v G R − > w G R v_{G^R}->w_{G^R} vGR−>wGR,此时的逆后序排序就是 v G R v_{G^R} vGR, w G R w_{G^R} wGR,所以我们会在构造函数中使用order.reversePost();
当进入dfs中时, v G R v_{G^R} vGR和 w G R w_{G^R} wGR的递归关系有两种:
第一种这意味着 w G R w_{G^R} wGR连接不到 v G R v_{G^R} vGR,这等价于 v G v_G vG连接不到 w G w_G wG;由于在第一部分中已经证明了如果v,w强连接,则v一定能访问到w,所以第一种关系是不存在的。
dfs(w)
...
w done
...
dfs(v)
...
v done
第二种意味着 v G R v_{G^R} vGR-> w G R w_{G^R} wGR,等价于 w G − > v G w_G -> v_G wG−>vG。
dfs(v)
...
dfs(w)
...
w done
...
v done
此时 v G − > w G v_G -> w_G vG−>wG 且 w G − > v G w_G -> v_G wG−>vG,结合第一部分的证明,此时该算法一定能够保证
- 构造函数一定能访问到所有与v相连通的w
- 构造函数所访问的所有w一定跟v是相连通的
命题P,中文420,最短路径的最优性条件
命题:IFF v 到 w的任意一条边 e,这些值满足 d i s t T o [ w ] < = d i s t T o [ v ] + e . w e i g h t ( ) distTo[w]<=distTo[v]+e.weight() distTo[w]<=distTo[v]+e.weight()时,他们是最短路径长度。
必要性:假设 d i s t T o [ w ] distTo[w] distTo[w]是s到w的最短路径,若存在s到w中的任意v有 d i s t T o [ w ] > d i s t T o [ v ] + e . w e i g h t ( ) distTo[w]>distTo[v]+e.weight() distTo[w]>distTo[v]+e.weight(),则意味着s到w的路径需要改道,即从s要经过v,然后才到w。此时 d i s t T o [ w ] distTo[w] distTo[w]就不是最短路径。
充分性:假设s与w是可达的,且最短路径为s->v0->v1->…vk->w则有以下式子
d
i
s
t
T
o
[
v
1
]
<
=
d
i
s
t
T
o
[
s
]
+
e
1.
w
e
i
g
h
t
(
)
;
d
i
s
t
T
o
[
v
2
]
<
=
d
i
s
t
T
o
[
v
1
]
+
e
2.
w
e
i
g
h
t
(
)
;
.
.
.
d
i
s
t
T
o
[
w
]
=
d
i
s
t
T
o
[
v
k
]
<
=
d
i
s
t
T
o
[
v
k
−
1
]
+
e
k
.
w
e
i
g
h
t
(
)
;
distTo[v1]<=distTo[s] + e1.weight();\\ distTo[v2]<=distTo[v1] + e2.weight();\\ ...\\ distTo[w] = distTo[vk]<=distTo[vk-1] + ek.weight();
distTo[v1]<=distTo[s]+e1.weight();distTo[v2]<=distTo[v1]+e2.weight();...distTo[w]=distTo[vk]<=distTo[vk−1]+ek.weight();
因为distTo[s]=0,则distTo[v1] <= e1.weight();则distTo[v2] <= e1.weigth() + e2.weight();所以最终有
d
i
s
t
T
o
[
w
]
<
=
e
1.
w
e
i
g
h
t
(
)
+
e
2.
w
e
i
g
h
t
(
)
+
.
.
.
+
e
k
.
w
e
i
g
h
t
(
)
;
distTo[w]<=e1.weight() + e2.weight() + ... + ek.weight();
distTo[w]<=e1.weight()+e2.weight()+...+ek.weight();
命题Q,中文P421,通用最短路径算法(非负权重)
命题:将distTo[s]初始化为0,其他则为无穷大,然后继续如下操作: r e l a x i n g relaxing relaxing G中任意边,直到不存在有效边为止。则对于任意从s可达的顶点w,在进行上述操作后,distTo[w]的值就是s到w的最短路径。
对于s可达的任意w,当distTo[w]是无穷大时,则必然会进行 r e l a x i n g relaxing relaxing操作,即存在e是有效边,最终distTo[w] = distTo[v] + e.weight(), v是s->w的某一处顶点;而对于非无穷大的distTo[w]来说,如果存在有效边,则其也会更新,且distTo[w_new] < distTo[w_old]。当有效边不再存在时,则意味着命题P成立。