定义
割点的定义(均在无向图中):在一个连通图中,如果有一个顶点集合,删除这个集合中的点以及相关的边之后,连通块的数量会增多.我们就称这个顶点集合为割点集合.如果这个点集合中只有一个点,那么这个点就叫做割点.
割边的定义(均在无向图中):在一个连通图中,如果删去其中一条边后,连通块的数量会增多,那么我们称这条边为桥或者是割边.
例如这个图:A和B都是割点,其中A-B为桥或割边
性质
1).若割点将一个图划分为两个部分U与W,那么所有的u∈U,v∈W,v 处于u到v的每一条路上.
2).每个非平凡的连通图中至少有两个点不是割点.
证明:每个非平凡的连通图中均存在一棵生成树,而非平凡的树上至少有两个度为1的顶点,而这两个顶点正好就是不是割点的两个点
DFS搜索树
1->2->3->5->4,然后我们可以根据这个序列画出一棵树.
我们把绿色的边称作树边(tree edge),黑色的边称作回边(back edge),通过回边可以访问到之前已经访问过的点.注意,我们常常在遇到一个点去访问它的父节点时,不会将回边表示出来.(如2与3)
这样,我们就得到了一棵DFS搜索树
Tarjan算法
首先选择一个根节点,从它开始进行DFS遍历.
对于根节点来说,判断它是否为割点是十分容易的.我们只需要看它的字树的数量即可.若它的子树的数量>=2,那么它必定为一个割点.(使得子树与子树之间失去了联系)
那么我们只需要求非根节点是否为割点即可.
首先,我们需要定义两个数组:low[]和dfn[].
dfn[u]为u点在上述dfs中是被访问到的第几个点。
low[u]为从u点及u点的子孙出发仅经过一次回边能到达的最小dfn。(如上图low[2]=1 2>3>1)
割点
如果一条边满足low[v]>=dfn[u],那么u点即为一个割点.
证明:如果存在这样一条边满足这样的性质,那么u的儿子v就永远不会访问到早于u的点,那么也就是说,从v出发形成的环中不会包括u,那么从u断开的话,就会形成两个或多个连通块,满足了割点的需求.
首先,low[u]可以先初始化为dfn[u],我们认为u至少能通过回边访问到自己(其实也就是没有回边).然后我们在遍历的过程中,考虑两种情况:一种是v点还没有被访问过,那么它的low[u]=min(low[u],low[v])(此时low[v]已经在dfs的过程中求出来了(在回溯的过程中),v能访问到的最早的点u同样也能访问到).
在过程中如果访问到一个已经访问过的点,那么有low[u]=min(low[u],dfn[v]).然后返回.
割边
割边的求法类似于割点.之前我们说到,在判断非根节点是否为割点时,我们采用了看low[v]>=dfn[u]的做法,在这里我们的做法更加简单.
不需要考虑根节点的问题,只需要判定low[v]>dfn[u]即可.(如之前给出来的图)
例题
题目描述
给出一个无向连通图, 求出所有割点与割边的数量。
输入
第1行: 2个整数N,M (1 <= N <= 5,000,N-1 <= M <= 10,000),分别表示顶点数和边数
接下来M行,每行2个整数,表示图中的一条边。
输出
第1行:1个整数,表示割点数
第2行:1个整数,表示割边数
样例输入
11 13
1 2
1 4
1 5
1 6
2 11
2 3
4 3
4 9
5 8
5 7
6 7
7 10
11 3
样例输出
4
3
代码
#include<cstdio>
#define MAXN 5000
#define MAXM 10000
struct node {
int v;
node *next;
}edge[MAXM * 2 + 5];
node *adj[MAXN+5];
node *ecnt = &edge[0];
int dfn[MAXN+5], low[MAXN+5];
bool vis[MAXN+5];
int n, m;
int cp = 0, ce = 0;
int dcnt = 0;
int rtson = 0;
void AddEdge(int u, int v) {
node *p = ecnt++;
p->v = v, p->next = adj[u], adj[u] = p;
p = ecnt++;
p->v = u, p->next = adj[v], adj[v] = p;
}
void Init() {
scanf("%d%d", &n, &m);
for(int i = 0; i < m; i++) {
int u, v;
scanf("%d%d", &u, &v);
AddEdge(u, v);
}
}
inline int min(int a, int b) {
return a < b ? a : b;
}
void dfs(int u, int fa) {
low[u] = dfn[u] = ++dcnt;
for(node * p = adj[u]; p != NULL; p = p->next) {
int v = p->v;
if(dfn[v] == 0) {
dfs(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= dfn[u]) {
if(fa != -1) {
if(!vis[u]) {
cp++;
vis[u] = true;
}
}
else
rtson++;
}
if(low[v] > dfn[u])
ce++;
}
else
if(v != fa)
low[u] = min(low[u], dfn[v]);
}
}
int main() {
Init();
dfs(1, -1);
if(rtson > 1) cp++;
printf("%d\n%d\n", cp, ce);
}