图论 桥与割点

在这里插入图片描述
在一个无向图中,如果删除其中一条边 使得整张图的联通分量数目发生改变,则这一条边称之为桥,入上图,点3与点5之间的一条边则是桥

在这里插入图片描述
在一个图中 可以有多个桥,例如上图

记录DFS顺序
ord数组代表当前dfs的顺序
low数组代表通过当前这个点 能访问到的最小的ord值
在这里插入图片描述
如果说有一条边 v-w low[w] > ord[v] 则说明边 v-w是桥,因为我们通过v-w这一条边以后,无法再回到比当前来说更早的节点,也就是说去掉这一条边之后,前面的点都到不了了,因此这一条边时桥

求桥的代码主要是通过tarjan算法
例题:https://www.luogu.com.cn/problem/P1656

具体操作步骤:
1:首先从起点开始dfs每一个点,在dfs的过程中,需要标记自己的父亲节点
2:访问下一个点,若下一个点不是自己的父亲节点,则分为两种情况考虑
情况1:下一个点没有被访问过,则继续dfs下一个点,dfs完成之后,更新当前节点low数组的值,结果为刚刚访问节点的low值,这时候我们去观察下一个节点的low值是否要大于当前节点的ord值,如果大于,则说明找到了一个桥
情况2:下一个点被访问过了,则直接更新当前节点的low值为下一个点的ord值(注意这里不是下一个点的low值,因为下一个点本身有可能访问到比他更早的节点,但是当前节点必须要通过下一个节点才能访问到更早的节点,因此更新的值为ord的值)。

#include <bits/stdc++.h>
using namespace std;
int n,m;
vector<int> G[200];
vector<pair<int,int> > res;
int Time = 1;
int low[300],ord[300],vist[300];
void work(int root,int dep)
{
	vist[dep] = 1;
	ord[dep] = low[dep] = Time;
	Time++;
	for(int i=0;i<G[dep].size();i++)
	{
		if(G[dep][i] != root)
		{
			if(vist[G[dep][i]] == 0)
			{
				work(dep,G[dep][i]);
				low[dep] = min(low[dep],low[G[dep][i]]);
				if(low[G[dep][i]] > ord[dep])
					res.push_back(make_pair(min(dep,G[dep][i]),max(dep,G[dep][i])));
			}
			else
			{
				low[dep] = min(low[dep],ord[G[dep][i]]);
			}
		}
	}
}
int main()
{
	cin >> n >> m;
	for(int i=1;i<=m;i++)
	{
		int v,w;
		cin >> v >> w;
		G[v].push_back(w);
		G[w].push_back(v);
	}
	for(int i=1;i<=n;i++)
		if(vist[i] == 0)
			work(0,i);
	sort(res.begin(),res.end());
	for(int i=0;i<res.size();i++)
		cout << res[i].first << " " << res[i].second << endl;
	
	return 0;
}

割点

对于一个无向图,将图中的一个顶点删除,使得图中的联通分量数发生改变,则这个顶点我们称为割点
下面这一张图中,有两个割点
在这里插入图片描述
寻找割点的算法与寻找桥的算法类似,依然是使用tarjan算法
在这里插入图片描述
对于一个顶点,如果下一个点的low值大于等于当前点的ord值,那么当前点则是一个割点,相对于寻找桥的算法来说,条件中 low值等于的情况也需要算在里面
如果这样去看的话,那么图中就有一个特殊情况,那就是根节点,因为根节点的ord值肯定是小于等于其他点的值的有,因此我们需要对根节点进行一次特殊判断
在这里插入图片描述当一个根节点有两个或者以上的孩子的时候,则说明根节点也是一个割点,这里的孩子是指DFS遍历过程中形成的遍历树中的孩子,不是指与根节点连了多少条边
在这里插入图片描述图中蓝色的线代表的是dfs的顺序,因此这图根节点的孩子节点只有一个,根节点不是割点
因此,我们只需要在dfs的过程中,用一个变量记录下当前节点的孩子节点,那么就能对根节点进行判断,要注意的是,我们找到的点有可能会出现重复,因此,我们需要进行去重复操作
例题https://www.luogu.com.cn/problem/P3388

#include <bits/stdc++.h>
using namespace std;
vector<int> G[20005];
set<int> res;
int dfn[20005],low[20005],Time=1;

void tarjan(int v,int parent)
{
	int child = 0;
	dfn[v] = low[v] = Time++;
	for(int i=0;i<G[v].size();i++)
	{
		if(dfn[G[v][i]] == 0)
		{
			child++;
			tarjan(G[v][i],v);
			low[v] = min(low[v],low[G[v][i]]);
			if(parent != v && low[G[v][i]] >= dfn[v])
				res.insert(v);
		}
		else if(G[v][i] != parent)
			low[v] = min(low[v],dfn[G[v][i]]);
	}
	if(parent==v && child >= 2)
		res.insert(v);
}

int main()
{
	int n,m;
	cin >> n >> m;
	for(int i=1;i<=m;i++)
	{
		int v,w;
		cin >> v >> w;
		G[v].push_back(w);
		G[w].push_back(v);
	} 
	for(int i=1;i<=n;i++)
		if(dfn[i] == 0)
			tarjan(i,i);
	cout << res.size() << endl;
	for(set<int>::iterator i=res.begin();i!=res.end();i++)
		cout << *i << " ";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值