點雙聯通:
阿新 • • 發佈:2019-01-06
計算點-雙連通分量(無重邊)的程式碼如下:
#include<cstdio> #include<cstring> #include<algorithm> #include<queue> #include<vector> #include<stack> using namespace std; const int maxn=1000+10; int n,m; int bcc_cnt; int dfs_clock; //bcc_cnt計數一共有多少個點-雙連通分量 int pre[maxn]; //vis標記,同時也標記了是樹中的第幾個點 bool iscut[maxn]; int bccno[maxn]; //bccno[i]=x表示第i個頂點屬於x號點雙連通分量 vector<int> G[maxn],bcc[maxn]; //bcc[i]中包含了i號點-雙連通分量的所有節點 struct Edge //邊的結構體 { int u,v; Edge(int u,int v):u(u),v(v){} }; stack<Edge> S; int dfs(int u,int fa) { int lowu=pre[u]=++dfs_clock; int child=0; for(int i=0;i<G[u].size();i++) { int v=G[u][i]; //取出點 Edge e = Edge(u,v); //建立這條邊 if(!pre[v]) //v沒有被訪問過 { S.push(e); //將邊入棧 child++; int lowv=dfs(v,u); //求low先 lowu=min(lowu,lowv); if(lowv >= pre[u]) //本節點是割點 { iscut[u]=true; bcc_cnt++; //注意bcc_cnt從1開始編號 bcc[bcc_cnt].clear(); //清除之前留下的 while(true) //產生一個雙連通分量, { Edge x=S.top(); //逐次取出邊 S.pop(); //1個點可能屬於多個連通分量,且它一定是割點。 if(bccno[x.u]!=bcc_cnt) //這個點還沒有統計到這個連通分量。 { bcc[bcc_cnt].push_back(x.u); bccno[x.u]=bcc_cnt; //預防重複統計 } if(bccno[x.v]!=bcc_cnt) { bcc[bcc_cnt].push_back(x.v); bccno[x.v]=bcc_cnt; } if(x.u==u && x.v==v) //掃到u-v,棧中又沒有與u相連的邊了。繼續試試其他孩子 break; } } } else if(pre[v]<pre[u]&&v!=fa) //點v在u上面就被訪問過,才可以更新,在下面訪問過的,不可以! { S.push(e); //這個是和u在一起的雙連通分量 lowu=min(lowu,pre[v]); } } /* 根的孩子必須大於1才會是割點,有割點才會有雙連通分量。 (1)那麼如果根不是割點呢? 假設根不是割點,那麼根最多隻有1個孩子,也就是說根的度為1,那麼根不可能處於任何1個雙連通分量中。 假設根是割點,那麼每個孩子各自是一個連通分量。那麼就會在上面的程式碼中被處理為一個雙聯通分量。 (2)如果有橋呢?比如u-v是橋,那麼會怎樣? 假設u-v是橋,且u在數中的時間戳比較小。可知v也就是一個割點啦,u-v斷開後,與v相連的都成為一個雙連通分量了。 回溯到u時,棧中(或頂)沒有包含u的邊,直到另一個連通分量的產生。 如果u的孩子中沒有連通分量了,那麼與u相連的孩子肯定有邊連到u的上邊,他們又形成了一個環了,雙連通分量又產生了,由其他割點去解決。 */ if(fa<0 && child==1) iscut[u]=false; return lowu; } void find_bcc(int n) { memset(pre,0,sizeof(pre)); memset(iscut,0,sizeof(iscut)); memset(bccno,0,sizeof(bccno)); dfs_clock = bcc_cnt = 0; for(int i=0;i<n;i++) //為了防止有多個連通圖,全部都得搜 if(!pre[i]) dfs(i,-1); } int main() { while(scanf("%d%d",&n,&m)==2&&n) { for(int i=0;i<n;i++) G[i].clear(); //點集 for(int i=0;i<m;i++) //輸入邊 { int u,v; scanf("%d%d",&u,&v); G[u].push_back(v); G[v].push_back(u); } find_bcc(n); //計算雙連通分量的個數 printf("點-雙連通分量一共%d個\n",bcc_cnt); for(int i=1;i<=bcc_cnt;i++) //輸出每個雙連通分量。可能點A在第一個雙連通分量中輸出,又出現在第2個雙連通分量中,因為它是割點。 { printf("第%d個點-雙連通分量包含以下點:\n",i); sort(&bcc[i][0],&bcc[i][0]+bcc[i].size()); //對vector排序,使輸出的點從小到大 for(int j=0;j<bcc[i].size();j++) { printf("%d ",bcc[i][j]); } printf("\n"); } } return 0; } 帶註釋的原始碼 帶註釋的原始碼
#include<cstdio> #include<cstring> #include<algorithm> #include<queue> #include<vector> #include<stack> using namespace std; const int maxn=1000+10; int n,m; int bcc_cnt; int dfs_clock;//bcc_cnt計數一共有多少個點-雙連通分量 int pre[maxn]; bool iscut[maxn]; int bccno[maxn];//bccno[i]=x表示第i個頂點屬於x號點雙連通分量 vector<int> G[maxn],bcc[maxn]; //bcc[i]中包含了i號點-雙連通分量的所有節點 struct Edge { int u,v; Edge(int u,int v):u(u),v(v){} }; stack<Edge> S; int dfs(int u,int fa) { int lowu=pre[u]=++dfs_clock; int child=0; for(int i=0;i<G[u].size();i++) { int v=G[u][i]; Edge e = Edge(u,v); if(!pre[v]) { S.push(e); child++; int lowv=dfs(v,u); lowu=min(lowu,lowv); if(lowv >= pre[u]) { iscut[u]=true; bcc_cnt++;//注意bcc_cnt從1開始編號 bcc[bcc_cnt].clear(); while(true) { Edge x=S.top(); S.pop(); if(bccno[x.u]!=bcc_cnt) { bcc[bcc_cnt].push_back(x.u); bccno[x.u]=bcc_cnt; } if(bccno[x.v]!=bcc_cnt) { bcc[bcc_cnt].push_back(x.v); bccno[x.v]=bcc_cnt; } if(x.u==u && x.v==v) break; } } } else if(pre[v]<pre[u] && v!=fa) //這個判斷條件如果少了,就是WA,可修改POJ2942程式碼 { S.push(e); lowu=min(lowu,pre[v]); } } if(fa<0 && child==1) iscut[u]=false; return lowu; } void find_bcc(int n) { memset(pre,0,sizeof(pre)); memset(iscut,0,sizeof(iscut)); memset(bccno,0,sizeof(bccno)); dfs_clock = bcc_cnt = 0; for(int i=0;i<n;i++) if(!pre[i]) dfs(i,-1); } int main() { while(scanf("%d%d",&n,&m)==2&&n) { for(int i=0;i<n;i++) G[i].clear(); for(int i=0;i<m;i++) { int u,v; scanf("%d%d",&u,&v); G[u].push_back(v); G[v].push_back(u); } find_bcc(n); printf("點-雙連通分量一共%d個\n",bcc_cnt); for(int i=1;i<=bcc_cnt;i++) { printf("第%d個點-雙連通分量包含以下點:\n",i); sort(&bcc[i][0],&bcc[i][0]+bcc[i].size()); //對vector排序,使輸出的點從小到大 for(int j=0;j<bcc[i].size();j++) { printf("%d ",bcc[i][j]); } printf("\n"); } } return 0; } /* 示例輸入: 6 7 1 2 2 3 1 3 3 4 4 5 3 5 5 6 輸出: 點-雙連通分量一共3個 第1個點-雙連通分量包含以下點: 5 6 第2個點-雙連通分量包含以下點: 3 4 5 第3個點-雙連通分量包含以下點: 1 2 3 */