在一個無向連通圖中,若是有一個頂點集合,刪除這個頂點集合,以及這個集合中全部頂點相關聯的邊之後,原圖變成多個連通塊,就稱這個點集爲割點集合。一個圖的點連通度的定義爲,最小割點集合中的頂點數。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 }
對於邊雙連通分支,求法更爲簡單。只需在求出全部的橋之後,把橋邊刪除,原圖變成了多個連通塊,則每一個連通塊就是一個邊雙連通分支。橋不屬於任何一個邊雙連通分支,其他的邊和每一個頂點都屬於且只屬於一個邊雙連通分支。
一個有橋的連通圖,如何把它經過加邊變成邊雙連通圖?方法爲首先求出全部的橋,而後刪除這些橋邊,剩下的每一個連通塊都是一個雙連通子圖。把每一個雙連通子圖收縮爲一個頂點,再把橋邊加回來,最後的這個圖必定是一棵樹,邊連通度爲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 }
本身的心得:
目前作的題目有:
1.求一個無向圖裏面的橋個數,而後再添加邊以後的橋的個數:具體作法就是先tarjan求出全部的橋和對應的low就是聯通份量。
以後收縮聯通塊而後成爲一棵樹,而後就是對應輸入邊用並查集去搜,若是在同一聯通塊裏面就沒影響,若是在兩聯通塊就合併聯通塊,每合併一次就減小一座橋。
2.求對應給出的無向圖是不是三聯通,先刪除一個點而後求出有無割點,有則不是三聯通。
3.構造雙聯通份量,用1的知識而後就出收縮數的葉子節點個數,最後(leaf+1)/2求出至少須要添加多少邊構成雙聯通。
4.求出點聯通份量的個數,求出橋聯通份量的個數。