求解双联通分量

参考算法指南白书P316

  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <iostream>
  4 #include <algorithm>
  5 #include <vector>
  6 #include <queue>
  7 #include <set>
  8 #include <map>
  9 #include <cstring>
 10 #include <cassert>
 11 #include <cstdlib>
 12 #include <time.h>
 13 #include <stack>
 14 using namespace std;
 15 
 16 const int maxn=1000+10;
 17 
 18 int n,m;
 19 int bcc_cnt;//bcc_cnt计数一共有多少个点-双连通分量
 20 int dfs_clock;//时间戳
 21 int pre[maxn];//pre标记,同时也标记了是树中的第几个点
 22 bool iscut[maxn];//是否割点
 23 int bccno[maxn];//bccno[i]=x表示第i个顶点属于x号点双连通分量
 24 vector<int> G[maxn],bcc[maxn];  //bcc[i]中包含了第i个双连通分量的所有节点
 25 
 26 struct Edge { //边的结构体
 27     int u,v;
 28     Edge(int u,int v):u(u),v(v) {}
 29 };
 30 
 31 stack<Edge> S;
 32 
 33 int dfs(int u,int fa) {
 34     int lowu=pre[u]=++dfs_clock;
 35     int child=0;
 36     for(int i=0; i<G[u].size(); i++) {
 37         int v=G[u][i];      //取出点
 38         Edge e = Edge(u,v); //创建这条边
 39         if(!pre[v]) { //v没有被访问过
 40             S.push(e);      //将边入栈
 41             child++;
 42             int lowv=dfs(v,u);  //求low先
 43             lowu=min(lowu,lowv);
 44             if(lowv >= pre[u]) { //本节点是割点
 45                 iscut[u]=true;
 46                 bcc_cnt++;              //注意bcc_cnt从1开始编号
 47                 bcc[bcc_cnt].clear();   //清除之前留下的
 48                 while(true) {           //产生一个双连通分量,
 49                     Edge x=S.top();     //逐次取出边
 50                     S.pop();
 51                     //1个点可能属于多个连通分量,且它一定是割点。
 52                     if(bccno[x.u]!=bcc_cnt) {   //这个点还没有统计到这个连通分量。
 53                         bcc[bcc_cnt].push_back(x.u);
 54                         bccno[x.u]=bcc_cnt;     //预防重复统计
 55                     }
 56                     if(bccno[x.v]!=bcc_cnt) {
 57                         bcc[bcc_cnt].push_back(x.v);
 58                         bccno[x.v]=bcc_cnt;
 59                     }
 60                     if(x.u==u && x.v==v)      //扫到u-v,栈中又没有与u相连的边了。继续试试其他孩子
 61                         break;
 62                 }
 63             }
 64         } else if(pre[v]<pre[u]&&v!=fa) { //点在u上面就被访问过啦
 65             S.push(e);      //这个是和u在一起的双连通分量
 66             lowu=min(lowu,pre[v]);
 67         }
 68     }
 69     /*
 70     根的孩子必须大于1才会是割点,有割点才会有双连通分量。
 71     (1)那么如果根不是割点呢?
 72     假设根不是割点,那么根最多只有1个孩子,也就是说根的度为1,那么根不可能处于任何1个双连通分量中。
 73     假设根是割点,那么每个孩子各自是一个连通分量。那么就会在上面的代码中被处理为一个双联通分量。
 74     (2)如果有桥呢?比如u-v是桥,那么会怎样?
 75     假设u-v是桥,且u在数中的时间戳比较小。可知v也就是一个割点啦,u-v断开后,与v相连的都成为一个双连通分量了。
 76     回溯到u时,栈中(或顶)没有包含u的边,直到另一个连通分量的产生。
 77     如果u的孩子中没有连通分量了,那么与u相连的孩子肯定有边连到u的上边,他们又形成了一个环了,双连通分量又产生了,由其他割点去解决。
 78     */
 79     if(fa<0 && child==1) iscut[u]=false;
 80     return lowu;
 81 }
 82 
 83 void find_bcc(int n) {
 84     memset(pre,0,sizeof(pre));
 85     memset(iscut,0,sizeof(iscut));
 86     memset(bccno,0,sizeof(bccno));
 87     dfs_clock = bcc_cnt = 0;
 88     for(int i=0; i<n; i++)          //为了防止有多个连通图,全部都得搜
 89         if(!pre[i]) dfs(i,-1);
 90 }
 91 int main() {
 92     while(scanf("%d%d",&n,&m)==2&&n) {
 93         for(int i=0; i<n; i++) G[i].clear(); //点集
 94         for(int i=0; i<m; i++) {    //输入边
 95             int u,v;
 96             scanf("%d%d",&u,&v);
 97             G[u].push_back(v);
 98             G[v].push_back(u);
 99         }
100         find_bcc(n);    //计算双连通分量的个数
101         printf("点-双连通分量一共%d个\n",bcc_cnt);
102         for(int i=1; i<=bcc_cnt; i++) { //输出每个双连通分量。可能点A在第一个双连通分量中输出,又出现在第2个双连通分量中,因为它是割点。
103             printf("第%d个点-双连通分量包含以下点:\n",i);
104             sort(&bcc[i][0],&bcc[i][0]+bcc[i].size()); //对vector排序,使输出的点从小到大
105             for(int j=0; j<bcc[i].size(); j++) {
106                 printf("%d ",bcc[i][j]);
107             }
108             printf("\n");
109         }
110     }
111     return 0;
112 }
View Code

 

posted @ 2016-03-25 21:47  yyblues  阅读(235)  评论(0编辑  收藏  举报