Kosaraju算法详解

Kosaraju算法是干什么的?

Kosaraju算法可以计算出一个有向图的强连通分量

什么是强连通分量?

在一个有向图中如果两个结点(结点v与结点w)在同一个环中(等价于v可通过有向路径到达w,w也可以到达v)它们两个就是强连通的,所有互为强连通的点组成了一个集合,在一幅有向图中这种集合数量就是这幅图的强连通分量的数量

怎么算??

第一步:计算出有向图 (G) 的反向图 (G反) 的逆后序排列(代码中有介绍)
第二步:在有向图 (G) 中进行标准的深度优先搜索,按照刚才计算出的逆后序排列顺序而非标准顺序,每次搜索访问的所有点即在同一强连通分量中

class Kosaraju {
    private Digraph G;
    private Digraph reverseG; //反向图
    private Stack<Integer> reversePost; //逆后续排列保存在这
    private boolean[] marked;
    private int[] id; //第v个点在几个强连通分量中
    private int count; //强连通分量的数量
    public Kosaraju(Digraph G) {
        int temp;
        this.G = G;
        reverseG = G.reverse();
        marked      = new boolean[G.V()];
        id          = new int[G.V()];
        reversePost = new Stack<Integer>();
        
        makeReverPost(); //算出逆后续排列
        
        for (int i = 0; i < marked.length; i++) { //重置标记
            marked[i] = false;
        }
        
        for (int i = 0; i < G.V(); i++) { //算出强连通分量
            temp = reversePost.pop();
            if (!marked[temp]) {
                count++;
                dfs(temp);
            }
        }
    }
    /*
     * 下面两个函数是为了算出 逆后序排列
     */
    private void makeReverPost() {
        for (int i = 0; i < G.V(); i++) { //V()返回的是图G的节点数
            if (!marked[i])
                redfs(i);
        }
    }
    
    private void redfs(int v) {
        marked[v] = true;
        for (Integer w: reverseG.adj(v)) { //adj(v)返回的是v指向的结点的集合
            if (!marked[w])
                redfs(w);
        }
        reversePost.push(v); //在这里把v加入栈,完了到时候再弹出来,弹出来的就是逆后续排列
    }
    /*
     * 标准的深度优先搜索
     */
    private void dfs(int v) {
        marked[v] = true;
        id[v] = count;
        for (Integer w: G.adj(v)) {
            if (!marked[w])
                dfs(w);
        }
    }
    
    public int count() { return count;}
}

为什么这样就可以算出强连通分量的数量?(比较费解)

比如有这样一个图,它有五个强连通分量


我们需要证明在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 的路径。

完整代码

package practice;

import java.util.ArrayList;
import java.util.Stack;

public class TestMain {
    public static void main(String[] args) {
        Digraph a = new Digraph(13);
        a.addEdge(0, 1);a.addEdge(0, 5);a.addEdge(2, 3);a.addEdge(2, 0);a.addEdge(3, 2);
        a.addEdge(3, 5);a.addEdge(4, 3);a.addEdge(4, 2);a.addEdge(5, 4);a.addEdge(6, 0);
        a.addEdge(6, 4);a.addEdge(6, 9);a.addEdge(7, 6);a.addEdge(7, 8);a.addEdge(8, 7);
        a.addEdge(8, 9);a.addEdge(9, 10);a.addEdge(9, 11);a.addEdge(10, 12);a.addEdge(11, 4);
        a.addEdge(11, 12);a.addEdge(12, 9);
        
        Kosaraju b = new Kosaraju(a);
        System.out.println(b.count());
    }
}

class Kosaraju {
    private Digraph G;
    private Digraph reverseG; //反向图
    private Stack<Integer> reversePost; //逆后续排列保存在这
    private boolean[] marked;
    private int[] id; //第v个点在几个强连通分量中
    private int count; //强连通分量的数量
    public Kosaraju(Digraph G) {
        int temp;
        this.G = G;
        reverseG = G.reverse();
        marked      = new boolean[G.V()];
        id          = new int[G.V()];
        reversePost = new Stack<Integer>();
        
        makeReverPost(); //算出逆后续排列
        
        for (int i = 0; i < marked.length; i++) { //重置标记
            marked[i] = false;
        }
        
        for (int i = 0; i < G.V(); i++) { //算出强连通分量
            temp = reversePost.pop();
            if (!marked[temp]) {
                count++;
                dfs(temp);
            }
        }
    }
    /*
     * 下面两个函数是为了算出 逆后序排列
     */
    private void makeReverPost() {
        for (int i = 0; i < G.V(); i++) { //V()返回的是图G的节点数
            if (!marked[i])
                redfs(i);
        }
    }
    
    private void redfs(int v) {
        marked[v] = true;
        for (Integer w: reverseG.adj(v)) { //adj(v)返回的是v指向的结点的集合
            if (!marked[w])
                redfs(w);
        }
        reversePost.push(v); //在这里把v加入栈,完了到时候再弹出来,弹出来的就是逆后续排列
    }
    /*
     * 标准的深度优先搜索
     */
    private void dfs(int v) {
        marked[v] = true;
        id[v] = count;
        for (Integer w: G.adj(v)) {
            if (!marked[w])
                dfs(w);
        }
    }
    
    public int count() { return count;}
}
/*
 * 图
 */
class Digraph {
    private ArrayList<Integer>[] node;
    private int v;
    public Digraph(int v) {
        node = (ArrayList<Integer>[]) new ArrayList[v];
        for (int i = 0; i < v; i++)
            node[i] = new ArrayList<Integer>();
        this.v = v;
    }
    
    public void addEdge(int v, int w) { node[v].add(w);}
    
    public Iterable<Integer> adj(int v) { return node[v];}
    
    public Digraph reverse() {
        Digraph result = new Digraph(v);
        for (int i = 0; i < v; i++) {
            for (Integer w : adj(i))
                result.addEdge(w, i);
        }
        return result;
    }
    
    public int V() { return v;}

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容