cut point and bridge總結

[點連通度與邊連通度]

在一個無向連通圖中,若是有一個頂點集合,刪除這個頂點集合,以及這個集合中全部頂點相關聯的邊之後,原圖變成多個連通塊,就稱這個點集爲割點集合。一個圖的點連通度的定義爲,最小割點集合中的頂點數。node

相似的,若是有一個邊集合,刪除這個邊集合之後,原圖變成多個連通塊,就稱這個點集爲割邊集合。一個圖的邊連通度的定義爲,最小割邊集合中的邊數。ios

 

 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)。ide

能夠看出,點雙連通與邊雙連通均可以簡稱爲雙連通,它們之間是有着某種聯繫的,下文中提到的雙連通,均既可指點雙連通,又可指邊雙連通。spa

 

 

[雙連通分支]

在圖G的全部子圖G'中,若是G'是雙連通的,則稱G'爲雙連通子圖。若是一個雙連通子圖G'它不是任何一個雙連通子圖的真子集,則G'爲極大雙連通子圖雙連通分支(biconnected component),或重連通分支,就是圖的極大雙連通子圖。特殊的,點雙連通分支又叫作3d

[求割點與橋]

該算法是R.Tarjan發明的。對圖深度優先搜索,定義DFS(u)爲u在搜索樹(如下簡稱爲樹)中被遍歷到的次序號。定義Low(u)爲u或u的子樹中能經過非父子邊追溯到的最先的節點,即DFS序號最小的節點。根據定義,則有:code

Low(u)=Min { DFS(u) DFS(v) (u,v)爲後向邊(返祖邊) 等價於 DFS(v)<DFS(u)且v不爲u的父親節點 Low(v) (u,v)爲樹枝邊(父子邊) }component

一個頂點u是割點,當且僅當知足(1)或(2) (1) u爲樹根,且u有多於一個子樹。 (2) u不爲樹根,且知足存在(u,v)爲樹枝邊(或稱父子邊,即u爲v在搜索樹中的父親),使得DFS(u)<=Low(v)。blog

一條無向邊(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.求出點聯通份量的個數,求出橋聯通份量的個數。

相關文章
相關標籤/搜索