tarjan总结

本文深入讲解Tarjan算法在有向图的强连通分量、无向图的边双联通分量、点双联通分量及割点的应用。通过具体代码示例,详细解释了如何计算dfn和low值,以及如何判断桥、割点和双联通分量。

今天学了一下tarjan的几种常见应用,发现我还是有很多知识盲点的。

 

有向图的强连通分量

这是最简单的应用,关键就是:

1、若dfn[y]=0,则dfs(y),low[x]=min(low[x],low[y])

2、否则,若y在栈中,则low[x]=min(low[x],dfn[y])。这是为了防止搜到别的分量中去。

代码如下:

int dfs(int z)
{
	int i;
	tim++;dfn[z]=tim;low[z]=tim;
	bz[z]=1;t++;d[t]=z;
	for(i=first[z];i>=1&&i<=m;i=nxt[i])
		if(dfn[way[i].y]==0)
		{
			dfs(way[i].y);
			if(low[way[i].y]<low[z])low[z]=low[way[i].y];
		}
		else
			if(bz[way[i].y]==1)
				if(dfn[way[i].y]<low[z])low[z]=dfn[way[i].y];
	if(dfn[z]==low[z])
	{
		while(t>=1)
		{
			bz[d[t]]=0;t--;
			if(d[t+1]==z)break;
		}
	}
}

 

无向图的边双联通分量

如果一个无向图中删掉了一条边之后这个图有联通变为不连通,那么这条边就是桥。

求无向图中的桥

首先说一下计算low的方式:

1、若dfn[y]=0,那么low[x]=min(low[x],low[y])

2、否则low[x]=min(low[x],dfn[y])

如果low[y]>dfn[x],那么边(x,y)就是桥。

注意一条边只能走一次。

边双联通分量

求出一个无向图中的桥,把这些桥全部删去,剩下的联通块就是边双联通分量了。

 

无线图的点双联通分量

割点

如果删除了一个点使无向图由联通变为不连通,那么这个点就是割点。

求无向图中的割点

首先low的计算方式与上面一样。

接着如果存在low[y]>=dfn[x],那么x就是一个割点。(注意这里要特判一下x是否为搜索树的根。如果是的话那么就要满足有两个y的low[y]>=dfn[x]在能保证x是割点。这个很好证明,如果x不是根,那么只要有一个y出现上述情况了,我们删掉x之后,y与x的父亲并不联通。但是如果x是根的话就不满足了,因为x没有父亲)

点双联通分量

对于每一个low[y]>=dfn[x],我们把栈中y及y之后的点弹出。这些弹出的点再加上x就是一个点双。

注意判断自环的情况。

一个x可能在多个点双中,而桥不可能在边双中。这使得点双比边双要难。

贴一下模板(求无向图中的割点):

#include<cstdio>
#include<cstdlib>
#include<cstring>
#define MAXN 20010
#define MAXM 200010

struct map
{
	int x;
	int y;
};
map way[MAXM];
int first[MAXN],nxt[MAXM],use[MAXM],dfn[MAXN],low[MAXN],bz[MAXN],root[MAXN],n,m,ans,tim;
int dfs(int z)
{
	int i;
	tim++;dfn[z]=tim;low[z]=tim;
	for(i=first[z];i>=1&&i<=m;i=nxt[i])
		if(use[i]==0)
		{
			use[i]=1;
			if(i<=m/2)use[i+m/2]=1;
			else use[i-m/2]=1;
			if(dfn[way[i].y]==0)
			{
				dfs(way[i].y);
				if(low[way[i].y]<low[z])low[z]=low[way[i].y];
			}
			else
				if(dfn[way[i].y]<low[z])low[z]=dfn[way[i].y];
			if(low[way[i].y]>=dfn[z])bz[z]++;
		}
}
int main()
{
int i,j,m1=0,x,y;
scanf("%d %d",&n,&m);
for(i=1;i<=m;i++)
{
	scanf("%d %d",&x,&y);
	if(x!=y){m1++;way[m1].x=x;way[m1].y=y;}
}
m=m1;
for(i=1;i<=m;i++)way[i+m].x=way[i].y,way[i+m].y=way[i].x;
m*=2;for(i=m;i>=1;i--)nxt[i]=first[way[i].x],first[way[i].x]=i;
for(i=1;i<=n;i++)
	if(dfn[i]==0){root[i]=1;dfs(i);}
for(i=1;i<=n;i++)
	if(root[i]==1&&bz[i]>=2||root[i]==0&&bz[i]>=1)ans++;
printf("%d\n",ans);
for(i=1;i<=n;i++)
	if(root[i]==1&&bz[i]>=2||root[i]==0&&bz[i]>=1)printf("%d ",i);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值