cut point and bridge总结

[点连通度与边连通度]

在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。一个图的点连通度的定义为,最小割点集合中的顶点数。

类似的,如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合。一个图的边连通度的定义为,最小割边集合中的边数。

 

 1 int dfn[maxn],low[maxn];
 2 int t=0;
 3 
 4 void Tarjan(int u,int p)
 5 {
 6     dfn[u]=low[u]=++t;
 7     
 8     int child=0;
 9     
10     for(int i=head[u];i!=-1;i=E[i].next)
11     {
12         int v=E[i].v;
13         
14         if(v==p) continue;//避免回连
15         
16         
17         if(!dfn[v])
18         {//树边
19             Tarjan(v,u);
20             child++;
21             low[u]=min(low[u],low[v]);
22             
23             if((u==p && child>1) || (u!=p && low[v]>=dfn[u]))
24                 cout<<"割点"<<u<<endl;
25         }
26         else
27             low[u]=min(low[u],dfn[v]);
28     }
29 }
求割点的临接表
 1 #include <iostream>
 2 #include <cstdio>
 3 
 4 using namespace std;
 5 
 6 bool adj[10][10];
 7 int visit[10];
 8 int grand[10];
 9 
10 int t=0;
11 
12 void DFS(int p,int i)
13 {
14     visit[i]=++t;
15     grand[i]=i;
16     
17     int child=0;
18     bool ap=false;
19     
20     for(int j=0;j<10;j++)
21         if(adj[i][j] && j!=p)
22         {
23             if(visit[j])
24             {
25                 if(visit[j]<visit[grand[i]])
26                     grand[i]=j;
27             }
28             else
29             {
30                 child++;
31                 DFS(i,j);
32                 
33                 if(visit[grand[i]]>visit[grand[j]])
34                     grand[i]=grand[j];
35                 
36                 if(visit[grand[j]]>=visit[i])
37                     ap=true;
38             }
39         }
40     
41     if((i==p && child>1) || (i!=p && ap))
42         cout<<""<<i<<"点是关节点"<<endl;
43 }
44 
45 void articulation_vertex()
46 {
47     memset(visit,0,sizeof(visit));
48     
49     t=0;
50     
51     for(int i=0;i<10;i++)
52         if(!visit[i])
53             DFS(i,i);
54 }
55 
56 int main()
57 {
58     int a,b;
59     memset(adj,0,sizeof(adj));
60     for(int i=0;i<11;i++)
61     {
62         scanf("%d%d",&a,&b);
63         adj[a][b]=adj[b][a]=1;
64     }
65     articulation_vertex();
66     return 0;
67 }
求割点的临接矩阵
 1 int dfn[maxn],low[maxn];
 2 int t=0;
 3 //low相同的为同一联通块,为缩点之后的。
 4 void Tarjan(int u,int p)
 5 {
 6     dfn[u]=low[u]=++t;
 7     
 8     int child=0;
 9     
10     for(int i=head[u];i!=-1;i=E[i].next)
11     {
12         int v=E[i].v;
13         
14         
15         
16         if(!dfn[v])
17         {//树边
18             Tarjan(v,u);
19             low[u]=min(low[u],low[v]);
20             
21         }
22         else if(v!=p)
23             low[u]=min(low[u],dfn[v]);
24     }
25 }
求桥的临接表
 1 #include <iostream>
 2 #include <cstdio>
 3 
 4 using namespace std;
 5 
 6 
 7 int adj[10][10];
 8 int visit[10];
 9 int low[10];
10 
11 int t=0;
12 
13 void DFS(int p,int i)
14 {
15     visit[i]=low[i]=++t;
16     
17     for(int j=0;j<10;j++)
18         if(adj[i][j])
19         {
20             if(!visit[j])
21             {
22                 DFS(i,j);
23                 
24                 low[i]=min(low[i],low[j]);
25                 
26                 if(low[j]>visit[i])
27                     cout<<"bridge:"<<i<<"---"<<j<<endl;
28             }
29             else if(j!=p || (j==p && adj[i][j]>=2))
30             {
31                 low[i]=min(low[i],low[j]);
32             }
33         }
34 }
35 
36 void bridge()
37 {
38     memset(visit,0,sizeof(visit));
39     t=0;
40     
41     for(int i=0;i<10;i++)
42         if(!visit[i])
43             DFS(i,i);
44 }
45 
46 int main()
47 {
48     int a,b;
49     memset(adj,0,sizeof(adj));
50     for(int i=0;i<11;i++)
51     {
52         scanf("%d%d",&a,&b);
53         adj[a][b]=adj[b][a]=1;
54     }
55     bridge();
56     return 0;
57     
58 }
59 
60 bridge
桥的临接矩阵

 

[双连通图、割点与桥]

如果一个无向连通图的点连通度大于1,则称该图是点双连通的(point biconnected),简称双连通重连通。一个图有割点,当且仅当这个图的点连通度为1,则割点集合的唯一元素被称为割点(cut point),又叫关节点(articulation point)

如果一个无向连通图的边连通度大于1,则称该图是边双连通的(edge biconnected),简称双连通或重连通。一个图有桥,当且仅当这个图的边连通度为1,则割边集合的唯一元素被称为桥(bridge),又叫关节边(articulation edge)。

可以看出,点双连通与边双连通都可以简称为双连通,它们之间是有着某种联系的,下文中提到的双连通,均既可指点双连通,又可指边双连通。

 

 

[双连通分支]

在图G的所有子图G'中,如果G'是双连通的,则称G'为双连通子图。如果一个双连通子图G'它不是任何一个双连通子图的真子集,则G'为极大双连通子图双连通分支(biconnected component),或重连通分支,就是图的极大双连通子图。特殊的,点双连通分支又叫做

[求割点与桥]

该算法是R.Tarjan发明的。对图深度优先搜索,定义DFS(u)为u在搜索树(以下简称为树)中被遍历到的次序号。定义Low(u)为u或u的子树中能通过非父子边追溯到的最早的节点,即DFS序号最小的节点。根据定义,则有:

Low(u)=Min { DFS(u) DFS(v) (u,v)为后向边(返祖边) 等价于 DFS(v)<DFS(u)且v不为u的父亲节点 Low(v) (u,v)为树枝边(父子边) }

一个顶点u是割点,当且仅当满足(1)或(2) (1) u为树根,且u有多于一个子树。 (2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得DFS(u)<=Low(v)。

一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v)。

[求双连通分支]

下面要分开讨论点双连通分支与边双连通分支的求法。

对于点双连通分支,实际上在求割点的过程中就能顺便把每个点双连通分支求出。建立一个栈,存储当前双连通分支,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足DFS(u)<=Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支。

 1 #include <iostream>
 2 #include <cstdlib>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <algorithm>
 6 
 7 #define maxn 10010
 8 
 9 struct edge
10 {
11     int v,next;
12 };
13 struct node
14 {
15     int cnt,u;
16 };
17 
18 
19 using namespace std;
20 edge E[10*maxn+5];//注意最多有十条边相连,对于每个点/re了几次?
21 node G[maxn];
22 int head[maxn];
23 int n,m,tp,t,ans;
24 int dfn[maxn],low[maxn];
25 
26 void add_edge(int u,int v)
27 {
28     E[tp].v=v;
29     E[tp].next=head[u];
30     head[u]=tp++;
31 }
32 
33 bool cmp(node a,node b)//题目要求排序
34 {
35     if(a.cnt==b.cnt) return a.u<b.u;
36     return a.cnt>b.cnt;
37 }
38 void Tarjan(int u,int p)//求割点
39 {
40     dfn[u]=low[u]=++t;
41     
42     int child=0;
43     for(int i=head[u];i!=-1;i=E[i].next)
44     {
45         int v=E[i].v;
46         if(v==p) continue;
47         if(!dfn[v])
48         {
49             Tarjan(v,u);
50             child++;
51             low[u]=min(low[u],low[v]);
52             
53             if((u==p && child>1) || (u!=p && low[v]>=dfn[u]))
54                 G[u].cnt++;//把割点和与之相连的边去掉之后能形成几个联通块
55         }
56         else if(v!=p)
57         {
58             low[u]=min(low[u],dfn[v]);
59         }
60     }
61     
62 }
63 
64 void output()
65 {
66     sort(G,G+n,cmp);//打印输出前m个数值
67     for(int i=0;i<m;i++)
68         printf("%d %d\n",G[i].u,G[i].cnt);
69 }
70 
71 int main()
72 {
73    while(scanf("%d%d",&n,&m),n+m)
74    {
75        memset(head,-1,sizeof(head));
76        memset(dfn,0,sizeof(dfn));
77        memset(low,0,sizeof(low));
78        for(int i=0;i<n;i++)
79        {
80            G[i].u=i;
81            G[i].cnt=1;
82        }
83        tp=t=0;
84     
85        int a,b;
86        while(scanf("%d%d",&a,&b),(a+b)!=-2)
87        {
88            add_edge(a,b);
89            add_edge(b,a);
90        }
91        Tarjan(0,0);
92        output();
93        printf("\n");
94    }
95     return 0;
96 }
求点联通分之数。并不输出块里的元素
 1 void Tarjan(int u,int p)
 2 {
 3     dfn[u]=low[u]=++t;
 4     for(int i=head[u];i!=-1;i=E[i].next)
 5     {
 6         int v=E[i].v;
 7         
 8         if(!dfn[v])//如果为树边
 9         {
10             Tarjan(v,u);
11             low[u]=min(low[u],low[v]);
12         }
13         else if(v!=p)//不走回头路且为后向边
14         {
15             low[u]=min(low[u],dfn[v]);
16         }
17     }
18     if(dfn[u]==low[u])
19         ans++;//边联通块数
20 }
View Code

 

对于边双连通分支,求法更为简单。只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分支。桥不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。

[构造双连通图]

一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。

统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。

 

 1 #include <iostream>
 2 #include <cstdlib>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <algorithm>
 6 
 7 #define maxn 5050
 8 
 9 struct edge
10 {
11     int v,next;
12 };
13 
14 using namespace std;
15 edge E[10010];
16 int head[maxn];
17 int n,m,tp,t,ans;
18 int dfn[maxn],low[maxn];
19 bool map[maxn][maxn];
20 
21 void add_edge(int u,int v)
22 {
23     E[tp].v=v;
24     E[tp].next=head[u];
25     head[u]=tp++;
26 }
27 
28 void Tarjan(int u,int p)
29 {
30     dfn[u]=low[u]=++t;
31     for(int i=head[u];i!=-1;i=E[i].next)
32     {
33         int v=E[i].v;
34         
35         if(!dfn[v])//如果为树边
36         {
37             Tarjan(v,u);
38             low[u]=min(low[u],low[v]);
39         }
40         else if(v!=p)//不走回头路且为后向边
41         {
42             low[u]=min(low[u],dfn[v]);
43         }
44     }
45     
46 }
47 
48 void output()
49 {
50     int cnt[maxn],num=0;
51     memset(cnt,0,sizeof(cnt));
52     
53     for(int i=1;i<=n;i++)
54         for(int j=head[i];j!=-1;j=E[j].next)
55         {
56             int v=E[j].v;
57             if(low[v]!=low[i])
58                 cnt[low[i]]++; //求出每个点的度,当成有向图求。如果无向图则最后判断,度要除以2
59         }
60     
61     for(int i=0;i<=n;i++)
62         if(cnt[i]==1)
63             num++;
64     printf("%d\n",(num+1)/2);
65 }
66 
67 int main()
68 {
69     scanf("%d%d",&n,&m);
70     memset(head,-1,sizeof(head));
71     memset(map,false,sizeof(map));//记录重边
72     memset(dfn,0,sizeof(dfn));//第一次访问。
73     tp=t=0;
74     
75     int a,b;
76     for(int i=0;i<m;i++)
77     {
78         scanf("%d%d",&a,&b);
79         if(!map[a][b])
80         {
81             add_edge(a,b);
82             add_edge(b,a);
83             map[a][b]=map[b][a]=true;
84         }
85     }
86     Tarjan(1,1);
87     output();
88     return 0;
89 }
View Code

 

 

 

自己的心得:

目前做的题目有:

  1.求一个无向图里面的桥个数,然后再添加边之后的桥的个数:具体做法就是先tarjan求出所有的桥和对应的low就是联通分量。

之后收缩联通块然后成为一棵树,然后就是对应输入边用并查集去搜,如果在同一联通块里面就没影响,如果在两联通块就合并联通块,每合并一次就减少一座桥。

2.求对应给出的无向图是否是三联通,先删除一个点然后求出有无割点,有则不是三联通。

3.构造双联通分量,用1的知识然后就出收缩数的叶子节点个数,最后(leaf+1)/2求出至少需要添加多少边构成双联通。

4.求出点联通分量的个数,求出桥联通分量的个数。

转载于:https://www.cnblogs.com/do-it-best/p/5513321.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值