Kosaraju算法与有向图的强连通分量

Kosaraju算法

今天在学习图论算法的过程中,接触到了一个挺有意思的算法;
首先,先明确一下强连通分量的定义:如果两个顶点v和w是互相可达的,则称它们是强连通的(两个顶点是强连通的当且仅当它们都在一个普通的有向环中);

接下来,不妨先来思考下面这两个问题:
原图G

  • 给定一幅有向图,给定的两个顶点是强连通的吗?
  • 给定一幅有向图,这副有向图中的强连通分量有多少个?

算法过程

通过上述的思考,我们可能对于Kosaraju算法有一个大致的了解,那么算法的实现思路又是怎么样的呢?

  1. 首先,我们先对原图G进行一遍DFS,同时将访问顶点的记录压入栈中(从栈中依次弹出的顶点顺序为该有向图的拓扑排序);

  2. 其次,从栈顶依次弹出顶点,在原图的反图(下图)中,按照拓扑排序的顺序,对反图G’ 进行DFS;
    反图G‘

  3. 此时,在外循环中进入反图DFS的次数便为有向图的强连通分量的个数;

细节分析

相信通过上述的算法描述,您应该对于该算法有了一个宏观的了解,相信在您的脑海中会有几个问题:

  • 为什么要将顶点访问记录压入栈?
  • 为什么又要对反图G’ 按照栈中的访问记录进行DFS?

那么,接下来就是对于上述问题的分析:

Q1: 为什么要将顶点访问记录压入栈?
我们对原图进行DFS,将顶点的访问记录压入栈,其目的是为了得到该有向图顶点的拓扑排序(依次出栈即可得到);然而拓扑排序的意义非常明确,就类似于大学内的课程都要有其先学课程一样,我们可以得到一张类似的“大学课程学习顺序表”;不同的强连通分量就好比不同的”专业课程“,我们的目标就是寻找有多少个不同的专业;

Q2:为什么又要对反图G’ 按照栈中的访问记录进行DFS?
反图的作用是可以将本来连通(非强连通)的强连通分量,按照原图的访问顺序无法再连通,从而将各个强连通分量分离出来(站在原图的访问顺序上来看); 我们现在已经得到了有向图中的顶点访问顺序表(类似于“大学课程学习表”),那么我们就从“每个专业”最基础的 “课程” 开始,相同专业的课程会在一次DFS中被标记,因此,我们最终通过计数进入第二次DFS的次数便可得到该有向图中的强连通分量的个数;

代码部分(求解上图的强连通分量个数)

DiGraph(顶点的邻接集可以使用List来代替,这里使用Bag是出于安全性来考虑):

public class DiGraph {
    private final int V;
    private int E;
    private Bag<Integer>[] adj;

    public DiGraph(int V) {//读取构造
        this.V = V;
        this.E = 0;
        adj = (Bag<Integer>[]) new Bag[V];
        for (int v = 0; v < V; v++) {
            adj[v] = new Bag<>();
        }
    }

    public int V() {
        return V;
    }

    public int E() {
        return E;
    }

    public void addEdge(int v, int w) {//由v--->w的一条路径
        adj[v].add(w);
        E++;
    }

    public Iterable<Integer> adj(int v) {
        return adj[v];
    }

    public DiGraph reverse() {//求出反向图
        DiGraph R = new DiGraph(V);
        for (int v = 0; v < V; v++) {
            for (int w : adj(v)) {
                R.addEdge(w, v);
            }
        }
        return R;
    }

    @Override
    public String toString() {
        String S = V + " 顶点, " + E + " 边\n";
        for (int v = 0; v < V; v++) {
            S += v + ": ";
            for (int w : this.adj(v)) {
                S += w + " ";
            }
            S += "\n";
        }
        return S;
    }
}

Kosaraju:

public class Kosaraju {
    public static void main(String[] args) {
        DiGraph G = new DiGraph(9);
        G.addEdge(0, 1);
        G.addEdge(1, 2);
        G.addEdge(2, 0);
        G.addEdge(0, 3);
        G.addEdge(3, 4);
        G.addEdge(4, 6);
        G.addEdge(4, 5);
        G.addEdge(6, 5);
        G.addEdge(5, 3);
        G.addEdge(4, 7);
        G.addEdge(7, 8);
        G.addEdge(8, 7);
        Kosaraju koa = new Kosaraju(G);
        System.out.println(koa.getCount());
    }

    private Set<Integer> visited;
    private Stack<Integer> order;
    private boolean[] marked;
    private int count;      //强连通分量的数量

    public Kosaraju(DiGraph G) {
        visited = new HashSet<>();
        order = new Stack<>();
        marked = new boolean[G.V()];
        //获取顶点遍历的逆后续
        for (int i = 0; i < G.V(); i++) {
            if (!visited.contains(i)) topological_dfs(G, i);
        }

        //到这里order已经包含(出栈顺序)对反图进行dfs遍历的顺序

        DiGraph revG = G.reverse();
        for (int i = 0; i < G.V(); i++) {
            if (!marked[i]) {
                get_dfs(revG, i);
                count++;//强连通分量的个数
            }
        }
    }

    public void topological_dfs(DiGraph G, int v) {
        visited.add(v);
        for (int w : G.adj(v)) {
            if (!visited.contains(w)) topological_dfs(G, w);
        }
        order.push(v);
    }

    public void get_dfs(DiGraph G, int v) {
        marked[v] = true;
        for (int w : G.adj(v)) {
            if (!marked[w]) get_dfs(G, w);
        }
    }

    public int getCount() {//获取强连通分量
        return count;
    }
}

最终结果

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值