tarjan算法 (割点和桥)

最近刚学习了tarjan算法,发一篇博客写一下自己的心得和理解。

在了解割点和桥之前,我们先理解什么是双连通。
双连通和强连通分别是应用于无向图和有向图中的,那么在学习双连通之前,请自行学习求强连通分量的tarjan算法。

在一张连通的无向图中,如果删去任意的一条连接u,v的边,但u, v仍然可以保持连通,那么我们可以称u,v之间是边双连通的

在一张连通的无向图中,如果删去任意的一个连接u,v的点,但u,v之间仍是连通的,那么我们可以称u,v之间是点双连通的。

接着我们来认识割点和桥
比如在这张图中,如果删去u或v节点,那么图中的连通分量将会增加,因此可以称u和v是这张无向图中的割点;相对应的,如果删去连接u,v的那条边,那么图中的连通分量也会增加,因此我们称连接u,v的边为

割点
在求割点时,我们要分两种情况:当前求得割点是否为根。
1.如果当前所求的点为根,那么我们只需要计算根的儿子节点数,如果大于等于2即可说明根为割点。
2.如果当前所求的点不为根,那么我们只要判断在当前点的所有儿子节点中,如果有一个儿子节点无法凭借除了父亲节点之外的点回到祖先节点,那么这个儿子节点的父亲节点就是割点。

借助一张图说明

如果u的儿子属于第一区域,他可以有别的路径回到祖先节点,那么u就不是割点。
如果u的儿子属于第二区域,他除了经过父亲节点外无法回到祖先节点,那么u就是割点

那么理解完割点的定义后,如何求解?
和tarjan算法一样,定义一个DFN为一个节点的时间戳,即dfs序,定义LOW为能返回的最早的时间戳。
如果这个节点u为非根节点,那么当这个点的儿子v的LOW[v] >= DFN[u]时,即无法返回到祖先节点,那么u就是一个割点。
当这个点为祖先节点时,如何求他的儿子个数呢?只要在每一次dfs回溯到根时,即u==root时,给孩子节点++即可。

接下来看一道割点的模板题洛谷3388

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int dfn[N], low[N], idx;
vector<int> g[N];
set<int> st;
int n, m;
void tarjan(int u, int root, int fa) {
	dfn[u] = low[u] = ++idx;
	int child = 0;
	for (auto v : g[u]) {
		if (!dfn[v]) {
			tarjan(v, root, u);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u] && u != root) st.insert(u);
			if (u == root) child++;
		}
		else if(v != fa)
			low[u] = min(low[u], dfn[v]);
	}
	if (child >= 2 && u == root) st.insert(u);
}

int main() {
	cin >> n >> m;
	for (int i = 0; i < m; i++) {
		int x, y;
		cin >> x >> y;
		g[x].push_back(y);
		g[y].push_back(x);
	}
	for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i, i, 0);

	cout << st.size() << endl;
	for (auto x : st) cout << x << " ";
}


和割点差不多,只要改一处LOW[v] > DFN[u] 就可以了,而且不需要考虑根节点的问题。

割边是和是不是根节点无关,原来我们求割点的时候是指儿子节点是不可能不经过父节点 回到祖先节点(包括父节点),所以顶点u是割点。但桥的判定中,如果桥还可以回到父节点,那么说明删除了u,v之间的边仍不能让连通分量增加,因此必须要求LOW[v] > DFN[u].

看一道割边的题目华华和月月逛公园

题目中描述的不一定要走的边其实可以转化为不是割边的边,因为如果这条是割边,割去之后仍是连通图,与猜测不符。所以题目就转化为求n-割边的数目,非常简单的模板题。

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int dfn[N], low[N], idx;
int n, m, ans;
vector<int> g[N];
void tarjan(int u, int fa) {
	dfn[u] = low[u] = ++idx;
	for (auto v : g[u]) {
        if(v == fa) continue;
 		if (!dfn[v]) {
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] > dfn[u]) ans++;
		}
        else
            low[u] = min(low[u], dfn[v]);
	}
}

int main() {
	cin >> n >> m;
	for (int i = 0; i < m; i++) {
		int x, y;
		cin >> x >> y;
		g[x].push_back(y);
		g[y].push_back(x);
	}
	
	tarjan(1, 0);
	cout << m - ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值