图的强连通
对于一个有向图顶点子集S,如果在S内任取两个顶点u和v,都能找到一条从u到v的路径,那么就称S是强连通的。
强连通分量:如果在强连通顶点集合S中加入其它任意顶点集合后,它都不再是强连通的,那么就称S是原图的一个强连通分量
1.Kosaraju
因为强连通分量内的顶点,其可达性不受变得方向的影响,因此在原图和边反向的图上分别进行一次dfs
#include <vector> #include <cstdio> #include <cstring> #include <stdlib.h> #include <iostream> #include <algorithm> using namespace std; const int INF=0x3f3f3f3f; int V,E; vector<int> G[100005]; vector<int> rG[100005]; vector<int> vs; bool used[100005]; int in[100005],out[100005]; int x[100005],y[100005],cmp[1000005],cnt[1000005]; void addedge(int from,int to){ G[from].push_back(to); rG[to].push_back(from); } void dfs(int v){ int i; used[v]=1; for(i=0;i<G[v].size();i++) if(!used[G[v][i]]) dfs(G[v][i]); vs.push_back(v); } void rdfs(int v,int k){ int i; used[v]=1; cmp[v]=k; for(i=0;i<rG[v].size();i++) if(!used[rG[v][i]]) rdfs(rG[v][i],k); } int scc(){ int i,k; memset(used,0,sizeof(used)); vs.clear(); for(i=0;i<V;i++) if(!used[i]) dfs(i); memset(used,0,sizeof(used)); k=0; for(i=vs.size()-1;i>=0;i--) if(!used[vs[i]]) rdfs(vs[i],k++); return k; } int main(){ int t,i,j,ans,cas,tmp; scanf("%d",&t); for(cas=1;cas<=t;cas++){ scanf("%d%d",&V,&E); for(i=0;i<V;i++){ G[i].clear(); rG[i].clear(); vs.clear(); } for(i=0;i<E;i++){ scanf("%d%d",&x[i],&y[i]); x[i]--,y[i]--; addedge(x[i],y[i]); } ans=scc(); printf("%d\n",ans); } return 0; }
图的双联通
1.在一个无向图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合(割集)
点连通度:最小割点集合中的顶点数
点双联通:如果一个无向连通图的点连通度大于1,则称该图是点双联通
当这个图的点连通度为1时,则割点集合的唯一元素被称为割点,又称关节点,给出求割点的代码,以poj1144为例
#include <vector> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <iostream> #include <algorithm> using namespace std; vector<int> G[105]; int dis[105],low[105],vis[105],cnt[105]; int k,kk; void dfs(int x){ int i,tmp; k++; dis[x]=low[x]=k,vis[x]=1; for(i=0;i<G[x].size();i++){ tmp=G[x][i]; if(!vis[tmp]){ dfs(tmp); low[x]=min(low[x],low[tmp]); if(low[tmp]>=dis[x]&&x!=1) cnt[x]++; else if(x==1) kk++; } else low[x]=min(low[x],dis[tmp]); } } int main(){ int n,i,a,b,ans; while(scanf("%d",&n)!=-1){ if(n==0) break; for(i=0;i<=n;i++) G[i].clear(); memset(vis,0,sizeof(vis)); memset(cnt,0,sizeof(cnt)); while(scanf("%d",&a)&&a){ while(getchar()!='\n'){ scanf("%d",&b); G[a].push_back(b); G[b].push_back(a); } } k=kk=ans=0; dfs(1); if(kk>=2) ans++; for(i=1;i<=n;i++) if(cnt[i]>=1) ans++; printf("%d\n",ans); } return 0; }
2.边双联通:如果一个无向连通图的边连通度大于1,则称该图是边双联通
当这个图的边连通度为1时,则割边集合的唯一元素被称为桥,又称关节边,给出求桥的代码,以zoj2588为例
#include <vector> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <iostream> #include <algorithm> using namespace std; const int INF=0x3f3f3f3f; struct edge{ int to,id,num; edge(int a,int b,int c){ to=a,id=b,num=c; } }; vector<edge> G[20005]; int low[20005],dis[20005],vis[20005],ans[20005]; int n,m,k,kk; void dfs(int x,int fa){ int i; vis[x]=1;low[x]=k;dis[x]=k++; for(i=0;i<G[x].size();i++){ edge e=G[x][i]; if(!vis[e.to]){ dfs(e.to,x); low[x]=min(low[x],low[e.to]); if(low[e.to]>dis[x]&&e.num==0) ans[kk++]=e.id; } else if(e.to!=fa) low[x]=min(low[x],dis[e.to]); } } int tarjan(){ k=kk=0; dfs(1,1); return kk; } int main(){ int T,a,b,i,j,l,ans1,flag; scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); for(i=0;i<=n;i++){ vis[i]=0; G[i].clear(); } for(i=1;i<=m;i++){ scanf("%d%d",&a,&b); flag=0; for(j=0;j<G[a].size();j++){ edge e=G[a][j]; if(e.to==b){ flag=G[a][j].num=1; for(l=0;l<G[b].size();l++){ edge ee=G[b][l]; if(ee.to==a) G[b][l].num=1; } } } if(flag==0){ G[a].push_back(edge(b,i,0)); G[b].push_back(edge(a,i,0)); } } ans1=tarjan(); printf("%d\n",ans1); sort(ans,ans+kk); for(i=0;i<kk;i++){ if(i!=kk-1) printf("%d ",ans[i]); else printf("%d\n",ans[i]); } if(T) printf("\n"); } return 0; }