Kosaraju算法详解
• Kosaraju算法是干什么的?
Kosaraju算法可以计算出一个有向图的强连通分量
• 什么是强连通分量?
在一个有向图中如果两个结点(结点v与结点w)在同一个环中(等价于v可通过有向路径到达w,w也可以到达v)它们两
个就是强连通的,所有互为强连通的点组成了一个集合,在一幅有向图中这种集合的数量就是这幅图的强连通分量的数量
• 怎么算??
第一步:计算出有向图 (G) 的反向图 (G反) 的逆后序排列(代码中有介绍)
第二步:在有向图 (G) 中进行标准的深度优先搜索,按照刚才计算出的逆后序排列顺序而非标准顺序,每次搜索访问的所有点即在同一强连通分量中
1 class Kosaraju { 2 private Digraph G; 3 private Digraph reverseG; //反向图 4 private Stack<Integer> reversePost; //逆后续排列保存在这 5 private boolean[] marked; 6 private int[] id; //第v个点在几个强连通分量中 7 private int count; //强连通分量的数量 8 public Kosaraju(Digraph G) { 9 int temp; 10 this.G = G; 11 reverseG = G.reverse(); 12 marked = new boolean[G.V()]; 13 id = new int[G.V()]; 14 reversePost = new Stack<Integer>(); 15 16 makeReverPost(); //算出逆后续排列 17 18 for (int i = 0; i < marked.length; i++) { //重置标记 19 marked[i] = false; 20 } 21 22 for (int i = 0; i < G.V(); i++) { //算出强连通分量 23 temp = reversePost.pop(); 24 if (!marked[temp]) { 25 count++; 26 dfs(temp); 27 } 28 } 29 } 30 /* 31 * 下面两个函数是为了算出 逆后序排列 32 */ 33 private void makeReverPost() { 34 for (int i = 0; i < G.V(); i++) { //V()返回的是图G的节点数 35 if (!marked[i]) 36 redfs(i); 37 } 38 } 39 40 private void redfs(int v) { 41 marked[v] = true; 42 for (Integer w: reverseG.adj(v)) { //adj(v)返回的是v指向的结点的集合 43 if (!marked[w]) 44 redfs(w); 45 } 46 reversePost.push(v); //在这里把v加入栈,完了到时候再弹出来,弹出来的就是逆后续排列 47 } 48 /* 49 * 标准的深度优先搜索 50 */ 51 private void dfs(int v) { 52 marked[v] = true; 53 id[v] = count; 54 for (Integer w: G.adj(v)) { 55 if (!marked[w]) 56 dfs(w); 57 } 58 } 59 60 public int count() { return count;} 61 }
• 为什么这样就可以算出强连通分量的数量?(稍微有些费解)
比如有这样一个图,它有五个强连通分量
我们需要证明在26行的dfs(temp)中找到的①全是点temp的强连通点,②且是它全部的强连通点
证明时不要忘了定义:v可通过有向路径到达w,w也可以到达v,则它俩强连通
先证明②:
用反证法,就假如对一个点(点w)深度优先搜索时有一个它的强连通点(点v)没找到。
如果没找到,那就说明 点v 已经在找其他点时标记过了,
但 点v 如果已经被标记过了,因为有一条 v -> w 的有向路径,那 点w 肯定也被找过了,
那就不会对 点w 深度优先搜索了。
假设不成立 (*^ω^*)
再证明①:
对一个点(点w)深度优先搜索时找到了一个点(点v),说明有一条 w -> v 的有向路径,再证明有一条 v -> w 的路径就行了,
证明有一条 v -> w 的路径,就相当于证明图G的反向图(G反)有一条 w -> v 的有向路径,
因为 点w 和 点v 满足那个 逆后序排列,而逆后序排列是在redfs(node)结束时将node加入栈,再从栈中弹出,
那说明反向图的深度优先搜索中redfs(v)肯定在redfs(w)前就结束了,
那就是两种情况:
■ redfs(v)已经完了redfs(w)才开始
■ redfs(v)是在 redfs(w)开始之后结束之前 结束的,也就是redfs(v)是在redfs(w)内部结束的
第一种情况不可能,因为 G反 有一条 v -> w 的路径(因为G有一条 w -> v 的路径),
满足第二中情况即在 G反 中有一条 w -> v 的路径。
终于证完了。。。。。。
完整代码:
1 package practice; 2 3 import java.util.ArrayList; 4 import java.util.Stack; 5 6 public class TestMain { 7 public static void main(String[] args) { 8 Digraph a = new Digraph(13); 9 a.addEdge(0, 1);a.addEdge(0, 5);a.addEdge(2, 3);a.addEdge(2, 0);a.addEdge(3, 2); 10 a.addEdge(3, 5);a.addEdge(4, 3);a.addEdge(4, 2);a.addEdge(5, 4);a.addEdge(6, 0); 11 a.addEdge(6, 4);a.addEdge(6, 9);a.addEdge(7, 6);a.addEdge(7, 8);a.addEdge(8, 7); 12 a.addEdge(8, 9);a.addEdge(9, 10);a.addEdge(9, 11);a.addEdge(10, 12);a.addEdge(11, 4); 13 a.addEdge(11, 12);a.addEdge(12, 9); 14 15 Kosaraju b = new Kosaraju(a); 16 System.out.println(b.count()); 17 } 18 } 19 20 class Kosaraju { 21 private Digraph G; 22 private Digraph reverseG; //反向图 23 private Stack<Integer> reversePost; //逆后续排列保存在这 24 private boolean[] marked; 25 private int[] id; //第v个点在几个强连通分量中 26 private int count; //强连通分量的数量 27 public Kosaraju(Digraph G) { 28 int temp; 29 this.G = G; 30 reverseG = G.reverse(); 31 marked = new boolean[G.V()]; 32 id = new int[G.V()]; 33 reversePost = new Stack<Integer>(); 34 35 makeReverPost(); //算出逆后续排列 36 37 for (int i = 0; i < marked.length; i++) { //重置标记 38 marked[i] = false; 39 } 40 41 for (int i = 0; i < G.V(); i++) { //算出强连通分量 42 temp = reversePost.pop(); 43 if (!marked[temp]) { 44 count++; 45 dfs(temp); 46 } 47 } 48 } 49 /* 50 * 下面两个函数是为了算出 逆后序排列 51 */ 52 private void makeReverPost() { 53 for (int i = 0; i < G.V(); i++) { //V()返回的是图G的节点数 54 if (!marked[i]) 55 redfs(i); 56 } 57 } 58 59 private void redfs(int v) { 60 marked[v] = true; 61 for (Integer w: reverseG.adj(v)) { //adj(v)返回的是v指向的结点的集合 62 if (!marked[w]) 63 redfs(w); 64 } 65 reversePost.push(v); //在这里把v加入栈,完了到时候再弹出来,弹出来的就是逆后续排列 66 } 67 /* 68 * 标准的深度优先搜索 69 */ 70 private void dfs(int v) { 71 marked[v] = true; 72 id[v] = count; 73 for (Integer w: G.adj(v)) { 74 if (!marked[w]) 75 dfs(w); 76 } 77 } 78 79 public int count() { return count;} 80 } 81 /* 82 * 图 83 */ 84 class Digraph { 85 private ArrayList<Integer>[] node; 86 private int v; 87 public Digraph(int v) { 88 node = (ArrayList<Integer>[]) new ArrayList[v]; 89 for (int i = 0; i < v; i++) 90 node[i] = new ArrayList<Integer>(); 91 this.v = v; 92 } 93 94 public void addEdge(int v, int w) { node[v].add(w);} 95 96 public Iterable<Integer> adj(int v) { return node[v];} 97 98 public Digraph reverse() { 99 Digraph result = new Digraph(v); 100 for (int i = 0; i < v; i++) { 101 for (Integer w : adj(i)) 102 result.addEdge(w, i); 103 } 104 return result; 105 } 106 107 public int V() { return v;} 108 109 }