参考资料:
[1]: 【图论】求无向连通图的割点
[2] : 深度优先生成树及其应用
1.割点与联通度
在无向连通图中,删除一个顶点v及其相连的边后,原图从一个连通分量变成了两个或多个连通分量,则称顶点v为割点,同时也称关节点(Articulation Point)。
一个没有关节点的连通图称为重连通图(biconnected graph)。
若在连通图上至少删去 k 个顶点才能破坏图的连通性,则称此图的连通度为k。
2.简单的例子
(图a) (图b) (图c)
图a为重联通图,图b为非重联通图;
图b的割点为 C,D;
3.高效求解割点的方法
在介绍算法之前,先介绍几个基本概念
- DFS搜索树:用DFS对图进行遍历时,按照遍历次序的不同,我们可以得到一棵DFS搜索树,如图(c)所示,(以图b的A为根节点)。
- 树边:(也称为父子边),在搜索树中的实线所示,可理解为在DFS过程中访问未访问节点 时所经过的边。
- 回边:(也称为返祖边、后向边),在搜索树中的 虚线 所示,可理解为在DFS过程中遇到 已访问节点 时所经过的边。
该算法是R.Tarjan发明的。观察DFS搜索树,我们可以发现有两类节点可以成为割点:
- 对根节点u,若其有两棵或两棵以上的子树,则该根结点 u 为割点;
- 对非叶子节点 u(非根节点),若其子树的节点均没有指向 u 的祖先节点的回边,说明删除u之后,根结点与u的子树的节点不再连通;则节点u为割点。
对于根结点,显然很好处理;但是对于非叶子节点,怎么去判断有没有回边是一个值得深思的问题。
我们用dfn[u]记录节点u在DFS过程中被遍历到的次序号,low[u]记录节点u或u的子树通过非父子边追溯到最早的祖先节点(即DFS次序号最小);
那么low[u]的计算过程如下:
对于情况2,当(u,v)为树边且 low[v] >= dfn[u]时,节点u才为割点。
该式子的含义:以节点v为根的子树所能追溯到最早的祖先节点要么为 v 要么为 u。
参考代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 #define mem(a,b) memset(a,b,sizeof(a)) 6 const int maxn=1e3+50; 7 8 int n,m; 9 int fa[maxn]; 10 int dfn[maxn]; 11 int low[maxn]; 12 int root; 13 bool vis[maxn]; 14 bool artPoint[maxn];//判断节点i是否为割点 15 int num; 16 int head[maxn]; 17 struct Edge 18 { 19 int to; 20 int next; 21 }G[2*maxn]; 22 void addEdge(int u,int v) 23 { 24 G[num].to=v; 25 G[num].next=head[u]; 26 head[u]=num++; 27 } 28 29 void DFS(int u,int f,int &k,int &rootSon) 30 { 31 fa[u]=f; 32 vis[u]=true; 33 dfn[u]=low[u]=++k; 34 if(f == root) 35 rootSon++; 36 for(int i=head[u];~i;i=G[i].next) 37 { 38 int v=G[i].to; 39 if(!vis[v])//父子边 40 { 41 DFS(v,u,k,rootSon); 42 43 if(low[v] >= dfn[u] && u != root) 44 artPoint[u]=true; 45 46 low[u]=min(low[u],low[v]); 47 } 48 else if(fa[u] != v)//返祖边 49 low[u]=min(low[u],dfn[v]); 50 } 51 } 52 int Solve() 53 { 54 int k=0; 55 root=1;//以任意合法的节点为根节点都可以 56 int rootSon=0;//根节点的儿子个数 57 DFS(root,-1,k,rootSon); 58 59 int ans=0; 60 for(int i=1;i <= n;++i) 61 ans += artPoint[i] ? 1:0; 62 ans += rootSon >= 2 ? 1:0; 63 return ans; 64 } 65 void Init() 66 { 67 num=0; 68 mem(head,-1); 69 mem(vis,false); 70 mem(artPoint,false); 71 } 72 int main() 73 { 74 //n个节点,m条边 75 while(~scanf("%d%d",&n,&m)) 76 { 77 Init(); 78 for(int i=1;i <= m;++i) 79 { 80 int u,v; 81 scanf("%d%d",&u,&v); 82 addEdge(u,v); 83 addEdge(v,u); 84 } 85 printf("此图的割点个数为:%d\n",Solve()); 86 } 87 return 0; 88 }