Tarjan求强连通 缩点 割点 割边

首先引入几个预备知识

1.如果一个无向图中每个顶点从所有其他顶点都是可达的,则称该图是连通的。图的连通分量“从....可达”的等价类.

2.如果一个有向图中任意两点互相可达,则该有向图是强连通的“相互可达”等价类

3.有向图G=(V,E)的强连通分量是一个最大节点集合C包含于V,对于该集合中任意节点u,v,节点u,v可以相互到达

4.深度优先搜索树,每个强连通分量对应一颗深度优先树

5.u,v在图G中可以相互到达当且仅当他们在图G的转置图中可以相互到达

缩点 强连通

注释掉的代码为强连通图判定

既cnt==1;

注意缩点时:只有出度为0的点只有1个时,这个点才能被其他所有点到达

判断强连通以及缩点时不能反向建边因此注释掉

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<stack> 
#include<set>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int low[maxn],dfn[maxn],now,root=1;
bool vis[maxn];
int belong[maxn];
int out[maxn];
int cnnt[maxn];
vector<int>g[maxn];
set<int>ans;//缩点 
int cnt;//统计强连通分量个数,既深度优先搜索森林中有多少颗树 
void init(){
	now=0;
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	cnt=0;
	memset(vis,0,sizeof(vis));
	memset(belong,0,sizeof(belong));
	memset(out,0,sizeof(out));
	memset(cnnt,0,sizeof(cnnt));
}
void addedge(int u,int v){
	g[u].push_back(v);
	//g[v].push_back(u);
}
stack<int>st;//存分量图节点 
void tarjan(int u){
	low[u]=dfn[u]=++now;
	vis[u]=1;
	st.push(u);
	int len=g[u].size();
	for(int i=0;i<len;i++){
		int v=g[u][i];
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v]){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){
		cnt++;
		int top;
		while(1){
			top=st.top();
			st.pop();//分量图节点出栈 
			belong[top]=cnt;//记录节点属于哪个强连通分量,既属于那颗深度优先搜索树 
			vis[top]=0;
			if(u==top)break;
		}
	}
}
//if(cnt==1)//整张图是一个强连通。既只有一颗深度优先搜索树
//缩点
//只有出度为零的点的个数等于1时,这个出度为零的点才能被其他所有点到达 
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	init();
	while(m--){
		int a,b;
		scanf("%d%d",&a,&b);
		addedge(a,b);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			tarjan(i);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=0;j<g[i].size();j++){
			if(belong[i]!=belong[g[i][j]])//不在一个分量内,且存在出边 既存在横向边 
			out[belong[i]]++;//出度++ 
		}
		cnnt[belong[i]]++;//统计分量内的节点数 
	}
	int temp=0;
	int ans;
	for(int i=1;i<=cnt;i++){
		if(out[i]==0){
			temp++;
			ans=cnnt[i];
		}
	} 
	if(temp==0||temp>1){
		cout<<0;
	}
	else if(temp==1)
	cout<<ans;
	return 0;
} 

割点,割边

求割点,割边时需要反向建边

割点:无向图删掉一点和它所关联的边,图的连通性增加。
割边(桥):无向图删掉一边,图的连通性增加。

求割点

- 根节点有多个子树

- 非根节点u,当儿子节点vdfn[u] <= low[v]

求割边

割边等价于dfn[u]<low[v]
模板如下

void tarjan(int u,int fa){
	low[u]=dfn[u]=++now;//开始时间,完成时间 
	int len=g[u].size();//获取邻接表的长度
	int son=0;//记录树根节点孩子数 
	for(int i=0;i<len;++i){
		int v=g[u][i];
		if(v==fa)continue;//后向边
		if(!dfn[v]){
			tarjan(v,u);
			son++;
			low[u]=min(low[u],low[v]);
			if(u==root&&son>1){
				ans.insert(u);
			}
			else if(u!=root&&dfn[u]<=low[v]){
				ans.insert(u);
			}
		}
		else{
			low[u]=min(low[u],dfn[v]);
		}
	}
}

部分内容引自算法导论

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值