Cut-point-algorithm
Cut-point-algorithm
割点和割边
既然已经写了有关缩点的博客,趁热打铁出一片关于割点的博客(没错,用到的算法还是Tarjan),其实Tarjan算法有好几个,但我感觉用起来都差不多,也就不细分了。废话少说,开始正篇。
本文重点以割点为例展开介绍,割边区别不大(就类似邻接表和链式前向星差不多),但代码都会展示。
定义
割点:当在一个连通的图中,去除某个点,使得该图不再连通,而被去除的点就是该图的割点。
割边(桥):如割点一样,在一个连通的图中,去除某条边,使得该图不再连通,而被去除的条边就是该图的割边,也称桥。
理论上如何判断割点和割边
首先,由于是Tarjan算法,所以不出意料我们需要引入一个时间轴(dfn)和连接轴(low)。
我们可以在脑海里想象一个图,1–2 ,2–3 ,3–4 ,4–2(无向图),我们从一端开始,假设它的编号为1,我们每次到达一个结点,都初始化dfn 和 low 当前时间。 (比如:我们从1结点开始 ,时间也从1开始,那么结点1的dfn 和 low都为1;结点2的dfn 和 low就为2……) 当结点来到4时,结点4指向的是2,此时,结点4的low 则改变为2;说明产生了回退边(该结点的孩子指向了当前结点之前)由于我们是通过DFS实现在回溯的同时更新前方已访问过的结点的low。
这些步骤结束之后,开始直观判断割点,如果该结点的dfn < 孩子的low,说明改结点是割点 。 否则,说明孩子与比自己当前位置还前的结点有连接。所以,就算去除自己,孩子还是会通过另外一条路径与整个图相连。但值得注意的是开始的端点结点,它的时间最早,所以不管它的孩子的low值怎么变化 它的dfn 都小于孩子的low, 显然并不是所有的端点都是割点 ,对于这种情况我们用孩子数量的办法,当端点的孩子数量 多于1时说明是割点。
割边判断方法一样,当前结点的u的dfn < 孩子结点v的low时,且不需要判断开始点不需要另外判断,则割边为:u–v;
实现
割点判断
1 | int cut[N], cnt; |
割点数求解
在割点的求解代码中没有对割点数目进行计算,只是进行了cut[]标记,所以如果需要割点是数目,需要将所有结点遍历一遍。
1 | for (int i = 1; i <= n; i++) { |
割边判断
1 | int dfn[N], low[N], id; |
模板题
【模板】割点(割顶)
【模板】割点(割顶)
题目背景
割点
题目描述
给出一个 个点, 条边的无向图,求图的割点。
输入格式
第一行输入两个正整数 。
下面 行每行输入两个正整数 表示 到 有一条边。
输出格式
第一行输出割点个数。
第二行按照节点编号从小到大输出节点,用空格隔开。
样例 #1
样例输入 #1
1
2
3
4
5
6
7
8
6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6
样例输出 #1
1
2
1
5
提示
1 | 6 7 |
样例输出 #1
1
2
1
5
提示
1 | 1 |
对于全部数据,,。
点的编号均大于 小于等于 。
tarjan图不一定联通。
AC代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using namespace std;
int n, m;
typedef struct edge {
int from, to, link;
} Edge;
typedef struct node {
int id;
} Node;
typedef struct graph {
int head[N];
int node_num = 0;
Node p[N];
int edge_num = 0;
Edge e[M];
} Graph;
Graph g;
void add_edge(int from, int to) {
g.e[++g.edge_num].to = to;
g.e[g.edge_num].from = from;
g.e[g.edge_num].link = g.head[from];
g.head[from] = g.edge_num;
}
int cut[N], cnt;
int dfn[N], low[N], id;
void tarjan(int step, int root) {
low[step] = dfn[step] = ++id;
int child = 0;
for (int k = g.head[step]; k != -1; k = g.e[k].link) {
int v = g.e[k].to;
if (!dfn[v]) {
tarjan(v, root);
low[step] = min(low[step], low[v]);
if (low[v] >= dfn[step] && step != root) {
cut[step] = 1;
}
if (step == root) {
child++;
}
}
low[step] = min(low[step], dfn[v]);
}
if (child >= 2 && step == root) {
cut[step] = 1;
}
}
int main() {
ios::sync_with_stdio(false);
cout.tie(NULL);
memset(g.head, -1, sizeof(g.head));
cin >> n >> m;
int u, v;
for (int i = 1; i <= m; i++) {
cin >> u >> v;
add_edge(u, v);
add_edge(v, u);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) {
tarjan(i, i);
}
}
for (int i = 1; i <= n; i++) {
if (cut[i]) {
cnt++;
}
}
cout << cnt << endl;
for (int i = 1; i <= n; i++) {
if (cut[i]) {
cout << i << " ";
}
}
return 0;
}
1 |
|
- Title: Cut-point-algorithm
- Author: Charles
- Created at : 2023-01-12 17:00:41
- Updated at : 2026-05-11 20:11:12
- Link: https://charles2530.github.io/2023/01/12/cut-point-algorithm/
- License: This work is licensed under CC BY-NC-SA 4.0.