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 }

 

posted @ 2017-09-09 11:29  zhangqi66  阅读(808)  评论(0编辑  收藏  举报