求无向连通图的割点

 

参考资料:

  [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搜索树,我们可以发现有两类节点可以成为割点:

  1. 对根节点u,若其有两棵或两棵以上的子树,则该根结点 u 为割点;
  2. 对非叶子节点 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 }
View Code

 


以上内容全部参考自大佬博客,下面谈谈我的心得:

  1.问法不同,定义的变量artPoint的状态就不同

    [1]:如果题目求得是割点的个数或者只让求出那些点是割点,那么,artPoint定义成bool型的就可以了。

    [2]:如果题目让求出删除某个割点所形成的联通子图的数量呢?这该怎么做呢?(poj 1523"SPF"

    答案是将artPoint定义成int型的,然后,将44行的语句 artPoint[u]=true 改成 artPoint[u]++即可,对于非根节点的割点 u ,将

    其删去后,会形成 artPoint[ u ]+1个联通子图。

    为什么这么做会对呢?

    对于这么一个图a

    

        图a          图b

    其形成的深度优先生成树为图b;

    显然,artPoint[3]=3,artPoint[3]+1就是将割点3删去后的联通子图的个数,那,如果④和⑤有边相连呢(图c)?

    

        图c         图d

    根据深度优先生成树的顺序,假设③节点先来到④节点,那么接下来的操作一定为DFS(4,5,k,rootSon),那么形成的深度优先生成树为图d;

    那么,当来到节点④时,由于low[5] < dfn[4]所以④不再是割点,那么最后求出的artPoint[3]=3;

    artPoint[3]+1还是删去割点3后形成的联通子图的个数。

  2.求解任意两点间的割点个数呢?[蓝桥杯][2013年第四届真题]危险系数

    具体戳这里👉解题报告

以上为目前的解题心得,持续更新中~~~~~~~

posted @ 2019-03-22 09:55 HHHyacinth 阅读(...) 评论(...)  编辑 收藏