Kosaraju算法(强连通分量分解)

对于一个有向图顶点的子集S,如果在S内取两个顶点u和v,都能找到一条从u到v的路径,那么就称S是强连通的。如果在强连通的顶点集合S中加入其他任意顶点集合后,它都不再是强连通的,那么就称S是原图的一个强连通分量(SCC:strongly connected component)。任意有向图都可以分解成若不相干的强连通分量,这就是强连通分量分解。把分解后的强连通分量缩成一个顶点,就得到了一个DAG(有向无环图)。

强连通分量分解可以通过两次简单的DFS实现。第一次DFS时,选取任意顶点作为起点,遍历所有尚未访问过的顶点,并在回溯前给顶点标号(post order,后序遍历)。对剩余的未访问过的顶点,不断重复上述过程。

完成标号后,越接近图的尾部(搜索树的叶子),顶点的标号越小。第二次DFS时,先将所有边反向,然后以标号最大的顶点为起点进行DFS。这样DFS所遍历的顶点集合就构成了一个强连通分量。之后,只要还有尚未访问的顶点,就从中选取标号最大的顶点不断重复上述过程。

可以将强连通分量缩点并得到DAG。此时可以发现,标号最大的节点就属于DAG头部(搜索树的根)的强连通分量。因此,将边反向后,就不能沿边访问到这个强连通分量以外的顶点。而对于强连通分量内的其他顶点,其可达性不受边反向后的影响,因此在第二次DFS时,可以遍历一个强连通分量里的所有顶点。

 

时间复杂度分析:只进行了两次DFS,因而总的时间复杂度时O(V+E)。


#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>

using namespace std;

const int max_v=100;

int V;
vector<int>g[max_v];
vector<int>rg[max_v];
vector<int>vs;
bool used[max_v];
int cmp[max_v];

void add_edge(int from,int to)
{
    g[from].push_back(to);
    rg[to].push_back(from);
}

void dfs(int v)
{
    used[v]=true;
    for(int i=0;i<g[v].size();i++){
        if(!used[g[v][i]]){
            dfs(g[v][i]);
        }
    }
    vs.push_back(v);
}

void rdfs(int v,int k)
{
    used[v]=true;
    cmp[v]=k;
    for(int i=0;i<rg[v].size();i++){
        if(!used[rg[v][i]]){
            rdfs(rg[v][i],k);
        }
    }
}

int scc()
{
    memset(used,0,sizeof(used));
    vs.clear();
    for(int v=0;v<V;v++){
        if(!used[v]){
            dfs(v);
        }
    }
    memset(used,0,sizeof(used));
    int k=0;
    for(int i=vs.size();i>=0;i--){
        if(!used[vs[i]]){
            rdfs(vs[i],k++);
        }
    }
    return k;
}

int main()
{
    scanf("%d",&V);
    int m;
    scanf("%d",&m);
    int u,v;
    for(int i=0;i<m;i++){
        scanf("%d%d",&u,&v);
        add_edge(u,v);
    }
    int ans=scc();
    printf("%d\n",ans);
    return 0;
}
/*
12
16
12 11
11 8
11 10
8 10
10 9
9 8
9 7
7 6
5 7
6 5
6 4
6 3
4 3
2 3
3 2
4 1
*/

猜你喜欢

转载自blog.csdn.net/zhouzi2018/article/details/81610804
今日推荐