嗯,首先边双连通分量(双连通分量之一)是:在一个无向图中,去掉任意的一条边都不会改变此图的连通性,即不存在桥(连通两个边双连通分量的边),称作边双连通分量。一个无向图的每一个极大边双连通子图称作此无向图的双连通分量。
对于边连通分量,我们需要先找出所有的桥,即为所有的桥做上标记。
首先要用dfs的性质来快速找出一个连通图中的所有的桥。
时间戳:表示在进行dfs的时候,每个节点被访问的先后顺序。每个节点会被标记两次,分别用 pre[],和post[]来表示。
在无向图中,只存在两种边,一种是树边(即边和点都没有被访问过),另一种是反向边(即边没有被访问过,但是点已经被访问过)。所以对于根节点而言,如果有两个及以上节点则根节点为割顶,否则不是
对于其他节点:在无向连通图G的DFS树中,非根节点u是割顶当且仅当u存在一个子节点v,使得v及其所有后代都没有反向边连回u的祖先(不包括u)
然后设low[u]为u及其后代所能连回的最早的祖先的pre[]值,则当u存在节点v使得low[v] >= pre[u]时,u 就为割顶;
而同理当 low[v] > pre[u] 时 u-v 是桥。
接下来直接上求图中割顶和桥的代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <vector> 5 using namespace std; 6 7 const int maxn = 1005; 8 int n,m; //n为点数,m为边数 9 int dfs_time; //时间戳 10 vector<int>G[maxn]; 11 int low[maxn],pre[maxn]; 12 int iscut[maxn];//会标记是否为割顶 13 14 int dfs(int u,int fa){ 15 int lowu = pre[u] = ++dfs_time; 16 int child = 0; 17 for(int i = 0;i < G[u].size();i++){ 18 int v = G[u][i]; //v是u所连接的点 19 if(!pre[v]) //没有访问过 20 { 21 child++; //孩子的节点数 22 int lowv = dfs(v,u); 23 lowu = min(lowu,lowv); //用后代更新lowu 24 25 //是割顶的判断条件 26 if(lowv >= pre[u]) 27 iscut[u] = 1; 28 29 //是桥的判断条件 30 if(lowv > pre[u]) 31 printf("%d -- %d 是桥 ",u,v); 32 } 33 else if(pre[v] < pre[u] && v != fa){ 34 //是反向边的情况,就更新lowu 35 lowu = min(lowu,pre[v]); 36 } 37 return lowu; //返回当前节点及其子节点能回到的最早祖先的pre值 38 } 39 } 40 41 int main(){ 42 while(scanf("%d%d",&n,&m)!=EOF){ 43 memset(pre,0,sizeof(pre)); 44 memset(iscut,0,sizeof(iscut)); 45 for(int i = 0;i <= n;i++) 46 G[i].clear(); 47 int u,v; //u -> v 48 for(int i = 0;i < m;i++){ 49 scanf("%d%d",&u,&v); 50 G[u].push_back(v); //在u中添加v 51 G[v].push_back(u); //在v中添加u(因为是无向图) 52 } 53 dfs(1,-1); //u是当前节点,fa是父节点 54 printf("割顶有:"); 55 for(int i = 1;i <= n;i++){ 56 if(iscut[i]) //如果是割顶 57 printf("%d ",i); 58 } 59 } 60 return 0; 61 }
第一步已经完成(对桥做标记)。然后利用dfs遍历连通分量,只不过在遍历的时候不能访问桥。
上代码:
1 #include <cstdio> 2 #include <algorithm> 3 #include <cstring> 4 #include <vector> 5 using namespace std; 6 7 const int maxn = 1000; 8 struct Edge 9 { 10 int no,v,next; //no:边的编号 11 }edges[maxn]; 12 13 int n,m,ebcnum; //节点数目,无向边的数目,边_双连通分量的数目 14 int e,head[maxn]; 15 int pre[maxn]; //第一次访问的时间戳 16 int dfs_clock; //时间戳 17 int isbridge[maxn]; //标记边是否为桥 18 vector<int> ebc[maxn]; //边_双连通分量 19 20 void addedges(int num,int u,int v) //加边 21 { 22 edges[e].no = num; 23 edges[e].v = v; 24 edges[e].next = head[u]; 25 head[u] = e++; 26 edges[e].no = num++; 27 edges[e].v = u; 28 edges[e].next = head[v]; 29 head[v] = e++; 30 } 31 32 int dfs_findbridge(int u,int fa) //找出所有的桥 33 { 34 int lowu = pre[u] = ++dfs_clock; 35 for(int i=head[u];i!=-1;i=edges[i].next) 36 { 37 int v = edges[i].v; 38 if(!pre[v]) 39 { 40 int lowv = dfs_findbridge(v,u); 41 lowu = min(lowu,lowv); 42 if(lowv > pre[u]) 43 { 44 isbridge[edges[i].no] = 1; //桥 45 } 46 } 47 else if(pre[v] < pre[u] && v != fa) 48 { 49 lowu = min(lowu,pre[v]); 50 } 51 } 52 return lowu; 53 } 54 55 void dfs_coutbridge(int u,int fa) //保存边_双连通分量的信息 56 { 57 ebc[ebcnum].push_back(u); 58 pre[u] = ++dfs_clock; 59 for(int i=head[u];i!=-1;i=edges[i].next) 60 { 61 int v = edges[i].v; 62 if(!isbridge[edges[i].no] && !pre[v]) dfs_coutbridge(v,u); 63 } 64 } 65 66 void init() 67 { 68 memset(pre,0,sizeof(pre)); 69 memset(isbridge,0,sizeof(isbridge)); 70 memset(head,-1,sizeof(head)); 71 e = 0; 72 ebcnum = 0; 73 } 74 75 int main() 76 { 77 int u,v; 78 while(scanf("%d%d",&n,&m)!=EOF) 79 { 80 init(); 81 for(int i=0;i<m;i++) 82 { 83 scanf("%d%d",&u,&v); 84 addedges(i,u,v); 85 } 86 dfs_findbridge(1,-1); //进行找桥 87 memset(pre,0,sizeof(pre)); 88 for(int i=1;i<=n;i++) 89 { 90 if(!pre[i]) 91 { 92 ebc[ebcnum].clear(); 93 dfs_coutbridge(i,-1); 94 ebcnum++; 95 } 96 } 97 for(int i=0;i<ebcnum;i++) 98 { 99 for(int j=0;j<ebc[i].size();j++) 100 printf("%d ",ebc[i][j]); 101 printf(" "); 102 } 103 } 104 return 0; 105 }