Tarjan算法求无向图的割点和桥(邻接矩阵&链式前向星(luoguP3388))

什么是割点:在无向图中,如果去掉一个点以及它的边后,图变得不连通了,那么这个点就是这个无向图的一个割点。

什么是桥:在无向图中,如果去掉一条边使得图不连通了,那么这条边就是无向图的一个桥。

判断割点有两种方法:1、x非root && x有儿子y && low[y]>=dfn[x]
2、x是root && x有>=2个儿子(这里的儿子是基于x的搜索树的,并不是原图上的,也就是说x能到达y并且y在之前没被访问过的时候y才是x的儿子)

判断桥的方法:low[y]>dfn[x]

要注意无向图儿子->父亲的边不作处理,因为如果处理的话,所有low[y]就都等于dfn[x]了

下面是邻接矩阵的写法:

/*判断割点有两种方法:1、x非root && x有儿子y && low[y]>=dfn[x]
					  2、x是root && x有>=2个儿子(这里的儿子是基于x的搜索树的,并不是原图上的,也就是说x能到达y并且y在之前没被访问过的时候y才是x的儿子)
  
  判断桥的方法:low[y]>dfn[x]
  
  要注意无向图儿子->父亲的边不作处理,因为如果处理的话,所有low[y]就都等于dfn[x]了 
*/ 
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1005;//点的最大数量 
int time=1;//过程中用来记录的时间戳 
int dfn[N],low[N],fa[N];//dfn记录每个点的时间戳;low记录每个点的追溯值;fa记录每个点的父亲,若无父亲则值为-1 
bool g[N][N];//存图 
int n,m,a,b;//n是点数,m是边数 
void dfs(int x)
{
	dfn[x]=low[x]=time++;//先更新时间戳 
	int child=0;//定义这个变量的目的是为了进行第二种判断割点的方法,这个变量记录的是一个节点搜索树意义上的儿子有几个。 
	for(int y=1;y<=n;++y)
	{
		if(g[x][y])//如果x能到y
		{
			if(!dfn[y])//并且y在之前还没被访问到,那么y就是x的一个搜索树意义下的儿子 
			{
				child++;//搜索树意义上的儿子数量++
				fa[y]=x;//记录y的父亲是x,方便之后dfs过程中以y为起点时不让y访问它的父亲x 
				dfs(y);//以y为起点进行dfs
				if(fa[x]==-1&&child>=2)//第二种判断割点的方式
				{
					cout<<x<<"是割点"<<endl; 
				} 
				if(fa[x]!=-1&&low[y]>=dfn[x])//第一种判断割点的方式,这里不用判断有无儿子,因为这个if本来就是有儿子的情况 
				{
					cout<<x<<"是割点"<<endl; 
				}
				if(low[y]>dfn[x])//判断是否是桥
				{
					 cout<<x<<"和"<<y<<"之间的边是桥"<<endl;
				}
				low[x]=min(low[x],low[y]);//更新x的追溯值,放在上面三个判断前面好像也行,因为判断只用到了x的时间戳= = 
			}
			else if(fa[x]!=y)//如果y不是x的儿子并且y不是x的父亲时,我们可以尝试更新x的追溯值 
			{
				low[x]=min(low[x],dfn[y]);
			}
		} 
	} 
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;++i)
	{
		cin>>a>>b;
		g[a][b]=1;
		g[b][a]=1;//无向图 
	}
	for(int i=1;i<=n;++i)
		fa[i]=-1;//先初始化fa数组,如果fa[x]==-1说明x是root,因为那意味着x没父亲 
	for(int i=1;i<=n;++i)
	{
		if(!dfn[i])//从没被访问过的点开始dfs 
			dfs(i);
	}
	return 0;
}

下面是链式前向星的例题及代码:
在这里插入图片描述

注意在某些特殊情况下,割点会重复被判断到,注意去重。(邻接矩阵和链式前向星都要注意这一点)

#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
map<int,int> mp;//用来去重
struct edge{
	int next;
	int to;
	int w;
}dat[200005];
int cnt=1;
int head[200005];
void add(int u,int v,int w)
{
	dat[cnt].w=w;
	dat[cnt].to=v;
	dat[cnt].next=head[u];
	head[u]=cnt++;
}
const int po = 2e4+5;
int timee=1;
int dfn[po],low[po],fa[po];
int n,m,a,b;
int num=0;
int gd[po];
int gd_i=0;
void dfs(int x)
{
	dfn[x]=low[x]=timee++;
	int child=0;
	for(int i=head[x];i;i=dat[i].next)//遍历和x相连的点
	{
		int y=dat[i].to;
		if(!dfn[y])
		{
			child++;
			fa[y]=x;
			dfs(y);
			if(fa[x]==-1&&child>=2)
			{
				if(mp[x]==0)
				{
					num++;
					mp[x]=1;
				}
			}
			if(fa[x]!=-1&&low[y]>=dfn[x])
			{
				if(mp[x]==0)
				{
					num++;
					mp[x]=1;
				}
			}
			low[x]=min(low[x],low[y]);
		}
		else if(fa[x]!=y)
		{
			low[x]=min(low[x],dfn[y]);
		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;++i)
	{
		cin>>a>>b;
		add(a,b,1);
		add(b,a,1);
	}
	for(int i=1;i<=n;++i)
		fa[i]=-1;
	for(int i=1;i<=n;++i)
	{
		if(!dfn[i])
			dfs(i);
	}
	cout<<num<<endl;
	for(map<int,int>::iterator j=mp.begin();j!=mp.end();++j)
	{
		if(j->second!=0)
			cout<<j->first<<" ";
	}
	cout<<endl;
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值