强联通分量(tarjan)

        有向图的极大强连通子图称为的强连通分量,强连通图只有一个强连通分量,即是其自身。非强连通的有向图有多个强连通分量,这里我介绍一种用tarjan的求强连通分量方法。

实现思路:

        首先先建图,然后在遍历的过程中对每个点的访问顺序打上标记。结果如下:

 

        可以看出2->3->6->4为一个强连通分量,那么接下来考虑如何将该连通分量的点值化为一个相同的数。

        这里可以建两个数组dfn和low,dfn用来记录该点在遍历时的顺序,low则用来记录该点所在的连通分量的顺序。

        那么该如何判断遍历过的点是否在一个连通分量上呢?因为连通分量上的点可以互相到达,又因为是有向图,所以强连通分量要么是一个点要么是一个环。即如果在访问的过程中找到一个访问过的点,就在回溯过程中将路径上点的low全部化为一个可以代表整个连通分量的数字,即访问该连通分量时的第一个访问的数字。

        实现代码如下 :

void tarjan(int x){
	last++;
	dfn[x]=last;
	low[x]=last;
	vis[x]=1;
	for(int i=head[x];i;i=e[i].next){
		int y=e[i].y;
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}else if(vis[x]==1)
			low[x]=min(low[x],dfn[y]);
	}vis[x]=2;
}

        但实际上这个代码是有问题的如果出现连个环共用两个及以上的点的话,后遍历的环就会被分成多个连通分量,如图:

        在这个图中7会被划分成一个额外的连通分量而不是和2->3->6->4划为一个整体, 为了解决这个问题,在这里我们可以用一个color数组去记录每个点所属的连通分量,通过判断下一个遍历点是否处于同一色块来判断是非需要划分。

        在遍历的过程中可以用一个栈来存储已经遍历过的点,在发现low[x]==dfn[x]时将栈里面的点都划上颜色,即到达该连通分量的第一点时进行划分。

void tarjan(int x){
	last++;
    dfn[x]=last;
    low[x]=last;
    stk.push(x);
	for(int i=head[x];i;i=e[i].next){
		int y=e[i].y;
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}else if(color[y]==0)low[x]=min(low[x],dfn[y]);
	}if(dfn[x]==low[x]){//判断是否位于该连通分量第一点
		++cidx;//新增一个颜色
		while(!stk.empty()){
			int t=stk.top();stk.pop();
			color[t]=cidx;//上色
			if(t==x)break;//染色完后退出
		}
	}
}

         最后输出每一个连通块遍历完后的颜色总数,完整代码如下:

#include<cstdio>
#include<stack>
using namespace std;
const int N=1e5+100;
int n,m;
struct node{
	int y,next;
}e[N];
stack<int>stk;
int head[N],color[N];
int tot,cidx,last;
int dfn[N],low[N];
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;} 
void add(int x,int y){//存图
	tot++;
	e[tot].y=y;
	e[tot].next=head[x];
	head[x]=tot;
}void tarjan(int x){
	last++;dfn[x]=last;low[x]=last;stk.push(x);
	for(int i=head[x];i;i=e[i].next){
		int y=e[i].y;
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}else if(color[y]==0)low[x]=min(low[x],dfn[y]);
	}if(dfn[x]==low[x]){
		++cidx;
		while(!stk.empty()){
			int t=stk.top();stk.pop();
			color[t]=cidx;
			if(t==x)break;
		}
	}
}int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		add(a,b);
	}for(int i=1;i<=n;i++)if(!color[i])tarjan(i);
	printf("%d\n",cidx);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值