求解双联通分量
参考算法指南白书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 }