先介绍一下割点
割点定义:
\qquad 在一个连通无向图中,如果将该点和该点所连的边去掉,该图不再连通,则称该点为割点
这个图中,2,3就是割点
Tarjan
这个算法可以用来求割点和连通分量,这里主要介绍求割点的部分。
主要思想是对一个图进行dfs,则其可以变为有回边的树
回边就是图比树所多出来的边
红色边即为回边
首先来思考一下什么样的情况下会产生割点
在这个图中,红色边为回边,以1为根节点,根节点有 2 2 2个子树,所以根节点就是割点 2 2 2这个结点,其子节点 3 , 5 3,5 3,5无法通过回边到达比 2 2 2更远的祖先,所以 2 2 2是割点。而 3 3 3的子节点 5 5 5可以通过回边到达比 3 3 3更远的祖先 2 2 2,所以 3 3 3不是割点
通过上面总结出如何判断一个结点是否是割点
1.
\mathbf{1.}
1.根节点如果有
2
2
2个或
2
2
2个以上的子树,则根节点为割点
2.
\mathbf{2.}
2.非根结点如果子节点无法通过回边到达比自己更远的祖先,则该结点为割点
下面介绍如何实现 T a r j a n Tarjan Tarjan算法
用两个数组来维护是否为割点,
d
f
n
[
u
]
dfn[u]
dfn[u]表示
u
u
u在dfs中第几个被访问到(即时间戳),
l
o
w
[
u
]
low[u]
low[u]表示
u
u
u通过非回溯边回到的最远祖先的
d
f
n
[
]
dfn[]
dfn[]值
当我们遍历到一条边
(
u
,
v
)
(u,v)
(u,v)有两种情况
1.
v
1.v
1.v未遍历过
\qquad
则如果回溯到
u
u
u时
l
o
w
[
v
]
<
d
f
n
[
u
]
low[v] < dfn[u]
low[v]<dfn[u]则
u
u
u为割点
2.
v
2.v
2.v已经遍历过
\qquad
则说明
(
u
,
v
)
(u,v)
(u,v)为回边,此时
d
f
n
[
v
]
dfn[v]
dfn[v]可能为
u
u
u的最远祖先,更新
l
o
w
[
u
]
low[u]
low[u]
特别的:当回溯到根节点时说明一棵子树遍历完了,将
c
h
i
l
d
+
1
child+1
child+1,如果
c
h
i
l
d
>
2
child>2
child>2则说明根节点也是割点
A C c o d e ACcode ACcode
#include<bits\stdc++.h>
const int MAXN = 2e5 + 10;
int dfn[MAXN],cnt,low[MAXN],child,sign;
bool cut[MAXN];
int head[MAXN];
int top;
struct Node{
int k,next;
}tr[MAXN];
using namespace std;
inline int read(){
int n = 0,l = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') l = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
n = (n << 1) + (n << 3) + (c & 15);
c = getchar();
}
return n * l;
}
void add(int x,int y){
tr[top].k = y;
tr[top].next = head[x];
head[x] = top ++;
}
void tarjan(int u,int fa){
dfn[u] = low[u] = ++sign;
for(int i = head[u]; i; i = tr[i].next){
int v = tr[i].k;
if(!dfn[v]){
tarjan(v,fa); //v该节点未访问过
low[u] = min(low[v],low[u]); //将儿子和自己访问的最远祖先作比较
if(u != fa && low[v] >= dfn[u] && !cut[u]){ //u不是根节点,且儿子v可以沿回路到达的最远祖先在u之下,则u为一个割点
cut[u] = 1;
cnt ++;
}
if(u == fa) child++; //返回到根节点则子树总数+1
}else low[u] = min(low[u],dfn[v]); //v结点访问过,则v可能为u的最远祖先,进行判断
}
if(u == fa && child > 1 && !cut[u]){
cut[u] = 1;
cnt ++;
}
}
int main(){
memset(dfn,0,sizeof(dfn));
int n = read(),m = read();
for(int i = 1; i <= m; i ++){
int x = read(),y = read();
add(x,y);
add(y,x);
}
for(int i = 1; i <= n; i ++)
if(!dfn[i]){
child = 0;
tarjan(i,i);
}
printf("%d\n",cnt);
for(int i = 1; i <= n; i ++)
if(cut[i]) printf("%d ",i);
return 0;
}