概念:
割点:有一个无向图G,如果删除某个顶点u以后,联通分量的数目增加,称u为图的关节点,或割点。
祖先:树形结构的概念,从根到该节点缩经分支上的所有节点。
子孙(后代):树形结构的概念,以某节点为根的紫书中的任一节点都成为该节点的子孙(后代)。
DFS森林(深度优先生成树): 从图中某一顶点出发,利用深度优先搜索遍历整个图,得到的包含树边和回边(反向边)的一颗树形结构。
树边:深度优先森林G‘中的边,如果顶点v是在寻找边(u,v)时候首次被发现的,那么(u,v)就是一条树边。G5中所有实线的边。
回边(反向边):深度优先森林G‘中的边,连接顶点u到它的某一祖先顶点v的边。G5中所有虚线的边。
图1:无向图G5
图2:G5的深度优先生成树
定理:
无向图割点的存在条件:
从图2中可以看出,DFS树中的割点有两类特性:
- 若生成树的根有两颗或者两颗以上的子树,则此根为割点。例如顶点A,删除以后生成2颗以上的子树。
- 若DFS树种某个非叶子顶点v,其某颗子树的根和子树中的其他节点均没有指向v的祖先的回边,则v为割点。例如,顶点B、D、G。
在无向连通图G的DFS树中,非根节点u是G的割点当且仅当u存在一个子节点v,使得v及其所有后代都没有反向边连回u的祖先(连回u不算)。
证明:如下图,考虑u的任意子节点v。如果v及其后代不能连回f,则删除u之后,f和v不再联通;反过来,如果v或它的某一个后代存在一条反向边连回f,则删除u后,以v根的整颗子树中的所有节点都可以利用这条反向边与f连通。如果所有子树中的节点都和f连通,根据“连通”关系的传递性,整个图就是连通的。
换句话说,low(v)表示v的子孙和v本身是否存在一条边能够连接回u,由于使用时间戳标记了dfs树,那么先被遍历的节点时间戳一定小于后被遍历的节点。图中u节点的时间戳就一定小于v和v的子孙的时间戳,时间戳存储在pre中。
那么,low中记录的值就可以用时间戳来表示,如果low(v)中存在一个时间戳小于u节点,那么就相当于v或者v的子节点中有一条边连到了u节点的祖先节点上,就像上图中的右侧。此时即使将u节点拿掉,也会有一条边从v或者v的子孙连到f上,并不影响连通性。
同理,如果low(v)大于等于pre(u),那么v节点或者v的子孙一定是有一条(这条边记录的是连接pre值最小的,也就是最靠近根节点的某个节点)边连接到了u的子孙当中,说明去掉u以后,v和v的子孙会形成一个联通分量。
代码:
下面数据对应图G,表示有13个顶点,16条边
13 16
a b
a c
a l
a f
b d
d e
c b
l m
m b
i g
g k
g h
g b
j m
j l
h b
#include<bits/stdc++.h>
using namespace std;
const int maxn=1001;//顶点数
int V,E;//顶点数和边数
vector<int> G[maxn];//邻接表
int is_cut[maxn];//每个顶点是否为割点,是1,不是0
int pre[maxn],dfs_clock;//每个节点的时间戳,时间戳记录
int low[maxn];//u及其后代所能连回的最招的祖先的pre值
int cut_vertex(int u,int father)
{
int lowu=pre[u] = ++dfs_clock;
int child = 0;
for(int i=0;i<G[u].size();i++)
{
int v = G[u][i];
if(!pre[v])
{
child++;
int lowv=cut_vertex(v,u);
lowu = min(lowu,lowv);//更新u节点保存的low(u)为u节点及其子孙节点之中pre值最小的
if(lowv>=pre[u])//如果u的某个子节点中v的low值大于等于当前节点u的时间戳,说明有边连到了u的子孙节点上
{
is_cut[u]=true;
}
}
else
{
/*
由于存储的是无向图的邻接表,如果有边a - b
那么G[a]={b},且G[b]={a}
v!=father就是为了防止a遍历了b以后反过来b遍历a
*/
if(pre[v]<pre[u]&&v!=father)
{
lowu=min(lowu,pre[v]);
}
}
}
if(father<0&&child==1)//根节点,而且只有一个子节点
{
is_cut[u]=0;
}
low[u]=lowu;
return lowu;
}
/*
A到K分别用 0到12表示
数据16条边13个点 对应图G
13 16
a b
a c
a l
a f
b d
d e
c b
l m
m b
i g
g k
g h
g b
j m
j l
h b
*/
int main()
{
ios::sync_with_stdio(false);
V=13;
E=16;
dfs_clock=0;
memset(pre,0,sizeof(pre));
int f,t;
char a,b;
for(int i=1;i<=E;i++)
{
cin>>a>>b;
f=a-'a';
t=b-'a';
G[f].push_back(t);//无向图
G[t].push_back(f);
}
cut_vertex(0,-1);
for(int i=0;i<13;i++)//13个顶点,是割点的输出
{
if(is_cut[i])
cout<<(char)(i+'a')<<" ";
}
cout<<endl;
return 0;
}
参考内容
刘汝佳的大白书
数据结构 严蔚敏