图论算法之(强连通分量<Kosaraju>)

强连通分量算法有3个之多,现在介绍这种名字叫做kosaraju算法。

这个算法基于两个事实,1.原图G与逆置图GT拥有相同的强连通分量,这肯定是正确的

2.任意一个子节点存放皆后于父节点,也就是说所有只有当所有子节点都入栈了,父节点才入栈

这种在递归调用之后将顶点入队列的方式叫逆后续排序(reverse post),在无环图中这种排序方式就是拓扑排序。

简要证明:

1. 第一次DFS有向图G时,最后记录下的节点必为最后一棵生成树的根节点。 
证明:假设最后记录下节点不是树根,则必存在一节点为树根,且树根节点必为此节点祖先;而由后根序访问可知祖先节点比此节点更晚访问,矛盾;原命题成立

2. 第一次DFS的生成森林中,取两节点A、B,满足:B比A更晚记录下,且B不是A的祖先(即在第一次DFS中,A、B处于不同的生成树中);则在第二次DFS的生成森林中,B不是A的祖先,且A也不是B的祖先(即在第二次DFS中,A、B处于不同的生成树中)。 
证明:假设在第二次DFS的生成森林中,B是A的祖先,则反图GT中存在B到A路径,即第一次DFS生成森林中,A是B的祖先,则A必比B更晚记录下,矛盾;假设在第二次DFS的生成森林中,A是B的祖先,则反图GT中存在A到B路径,即第一次DFS生成森林中,B是A的祖先,矛盾;原命题成立

3. 按上述步骤求出的必为强连通分量 
证明:首先,证明2保证了第二次DFS中的每一棵树都是第一次DFS中的某棵树或某棵树的子树。其次,对于第二次DFS中的每棵树,第一次DFS保证了从根到其子孙的连通性,第二次DFS保证了根到子孙的反向连通性(即子孙到根的连通性);由此,此树中的每个节点都通过其根相互连通。

 

从以上的理解可以看出,Kosaraju算法是有局限的,比如图中强连通分量是嵌套的,Kosaraju就会出现麻烦了。

代码挺短的,但是调了一晚上

#include<cstdio>
#include<cstring>
#define N 10000+10
#define M 500000+10
using namespace std;
int head1[N],num1,head2[N],num2;
struct edge{
    int next,to;
}e1[M],e2[M];
int stack[N],vis[N],scc[N],scc_cnt;
void add1(int from,int to)
{
    e1[++num1].next=head1[from];
    e1[num1].to=to;
    head1[from]=num1;
}
void add2(int from,int to)
{
    e2[++num2].next=head2[from];
    e2[num2].to=to;
    head2[from]=num2;
}
int tot;
void Kosaraju1(int u)
{
    vis[u]=1;
    for(int i=head1[u];i;i=e1[i].next)
    {
        int v=e1[i].to;
        if(!vis[v])Kosaraju1(v);
    }
    stack[++tot]=u;
}
void Kosaraju2(int u)
{
    scc[u]=scc_cnt;
    //vis[u]=0;
    for(int i=head2[u];i;i=e2[i].next)
    {
        int v=e2[i].to;
        if(!scc[v])
        Kosaraju2(v);
    }    
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i<=m;i++){scanf("%d%d",&x,&y);add1(x,y);add2(y,x);}
    for(int i=1;i<=n;i++) if(!vis[i])Kosaraju1(i);
    for(int i=n;i>=1;i--){
        if(!scc[stack[i]]){scc_cnt++;Kosaraju2(stack[i]);}
    }
    printf("%d",scc_cnt);
    return 0;
原文地址:https://www.cnblogs.com/star-eternal/p/7604811.html