Tarjan求强连通分量

【强连通分量】:

有向图强连通分量:

在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。

如果有向图G的每两个顶点都强连通,则称G是一个强连通图。

非强连通图有向图的极大强连通子图,成为强连通分量(strongly connected components)。

直接根据定义,用双向遍历取交际的方法求强连通分量,时间复杂度为O(N^2+M)。更好的方法是Kosaraju算法或者Tarjan算法。

两者的时间复杂度都是O(N+M)。本文介绍的是Tarjan算法。


【算法原理】(Tarjan)

Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一颗子树。

搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以盘对栈顶到栈中的节点是否为一个强连通分量。


定义DFN(u)为节点u搜索的次序编号(时间戳)。Low(u)为u或者u的子树能够追溯到的最早的栈中的节点的次序号。

由定义可以得出:

Low(u)= Min { DFN(u), Low(v)} ((u,v)为树枝边,u为v的父节点DFN(v),(u,v)为指向栈中节点的后向边(非横叉边))

当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。


【算法时空复杂度】:O(N+M)


附代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 1010
using namespace std;
int n,m,c=1,top=1,d=1,s=0,ans=0;
int cstack[MAXN],deep[MAXN],low[MAXN],head[MAXN],colour[MAXN],num[MAXN];//deep数组就是 时间戳dfn数组,且用了数组模拟的栈。。。
bool beque[MAXN];
struct node{
       int next,to;
}a[MAXN*5];//前向星存图
inline int read(){
       int date=0,w=1;char c=0;
       while(c!='-'&&(c<'0'||c>'9'))c=getchar();
       if(c=='-'){w=-1;c=getchar();}
       while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
       return date*w;
}
void add(int x,int y){
     a[c].to=y;
     a[c].next=head[x];
     head[x]=c++;
}
void work(int x){//Tarjan算法
     int t;
     beque[x]=true;
     deep[x]=low[x]=d++;
     cstack[top++]=x;
     for(int i=head[x];i;i=a[i].next){
             t=a[i].to;
             if(!deep[t]){
                          work(t);
                          low[x]=min(low[x],low[t]);
                          }
             else if(beque[t])
             low[x]=min(low[x],deep[t]);
             }
     if(low[x]>=deep[x]){
                         s++;
                         do{
                             beque[cstack[top-1]]=false;
                             colour[cstack[top-1]]=s;
                             }while(cstack[--top]!=x);
                         }
}
int main(){
    int x,y;
    n=read();m=read();
	memset(num,0,sizeof(num));
    for(int i=1;i<=m;i++){
            x=read();y=read();
            add(x,y);
            }
    for(int i=1;i<=n;i++)
    if(!deep[i])
    work(i);
	for(int i=1;i<=n;i++){
            num[colour[i]]++;
		    if(num[colour[i]]>num[num[0]])
			num[0]=colour[i];
            }
   for(int i=1;i<=n;i++)
   if(colour[i]==num[0])
   ans++;
   printf("%d\n",ans);//强连通分量中节点个数
   for(int i=1;i<=n;i++)
   if(colour[i]==num[0])
   printf("%d ",i);//强连通分量中各个节点
   return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值