割点与割边

创建日期:2025-02-07


割点和割边的定义

割点:

对于无向图 \(G\),若存在节点 \(v\) 使删除 \(v\) 及与 \(v\) 相连的边后,连通块数量增加,则称 \(v\)\(G\) 的割点(又称割顶)。

注意:只有点数 \(\ge 3\) 的无向图才有可能有割点。

割边:

对于无向图 \(G\),若存在边 \(e\) 使删除 \(e\)后,连通块数量增加,则称 \(e\)\(G\) 的割边(又称桥)。

注意:只有点数 \(\ge 2\) 的无向图才有可能有割边。

割点和割边的判定方法

割点:

我们使用Tarjan来判定割点。

设有点 \(x\)\(x\) 的子节点 \(y\).

\(low_y \ge dfn_x\),则删除 \(x\) 后,从 \(y\) 出发,一定到达不了 \(x\)\(x\) 的祖先、\(y\) 的兄弟节点,\(y\)\(y\) 的后代从图中分割出,故 \(x\) 可能是原图的割点。

易知,若 \(x\) 的度数为1,即使删除 \(x\),连通块数量也不会发生改变。故当 \(x\) 不是叶子节点且 \(degree_x = 1\) 时,即使存在子节点 \(y\) 满足 \(low_y > dfn_x\)\(x\) 也不为割点。

什么节点可能同时满足非叶子且度数为1呢?只可能是搜索树的根!所以割点的判定条件应该为存在 \(x\) 的孩子 \(y\) 满足:

\[low_y \ge dfn_x \&\& (x \ne root || degree_x \ge 2) \]

其中,\(root\) 为当前搜索树的根。

为了防止原图中存在重边,可以统计 \(x\) 的孩子数量 \(cnt\),并将条件改为:

\[low_y \ge dfn_x \&\& (x \ne root || cnt \ge 2) \]

由上可得出Tarjan函数:

void Tarjan(int u, int root) {
	low[u] = dfn[u] = ++tot;
	int cnt = 0;
	for (int v : e[u]) {
		if (!dfn[v]) {
			Tarjan(v, root);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u] && (u != root || ++cnt >= 2))
				cut[u] = true;
		} else
			low[u] = min(low[u], dfn[v]);
	}
}

割边:

割边的判定方法与割点相似,判定条件仅为:

\[low_y > dfn_x \]

但由于无向图会建立双向边,\(y\) 一定可以找到 \(x\) 并通过 \(low_v = \min(low_v, dfn_u)\) 更新 \(low_v\),所以一定有 \(low_y \le dfn_x\)。为了解决这个问题,我们可以为每组双向条边存储一个编号,并记录每个点是从哪个编号的边来的,在遍历是跳过编号相同的边。

注意:因为图中可能存在重边,所以不能记录每个点从哪个点来的,一定要记录是从哪个编号的边来的。

模版代码如下:

struct edge {int v,id;};

void Tarjan(int u, int from) {
	low[u] = dfn[u] = ++tot;
	for (edge tmp : e[u]) {
		int v = tmp.v, id = tmp.id;
		if (id == from)
			continue;
		if (!dfn[v]) {
			Tarjan(v, id);
			low[u] = min(low[u], low[v]);
			if (low[v] > dfn[u])
				bridge[id] = true;
		} else
			low[u] = min(low[u], dfn[v]);
	}
}
posted @ 2026-05-19 11:57  xubaichuan  阅读(22)  评论(0)    收藏  举报