首页 > 代码库 > 连通图基本知识
连通图基本知识
看了LRJ的训练指南上连通有关的介绍,写得挺好,但是有些位置逻辑跳跃比较大,还有一些留给读者思考的位置,在此做个总结.
1.DFS框架
2.连通分量
3.二分图判定
4.无向图的割顶和桥
5.无向图的双连通分量
6.有向图的强连通分量(Tarjan算法)
1.DFS框架
连通图很多都是跟DFS框架里面的previsit()和posvisit()有关,不多说.
vector<int> G[maxn]; int vis[maxn]; void dfs(int u) { vis[u]=1; previsit(u); for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(!vis[v]) dfs(v); } posvisit(u); }
2.无向图的连通分量
定义:在无向图中,如果从结点u可以到达结点v,那么从结点u也必然可以到达u。
求法
a.建图+dfs
vector<int> G[maxn],cc[maxn]; int vis[maxn]; int current_cc; void dfs(int u) { vis[u]=1; cc[current_cc].push_back(u); for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(vis[v]) continue; dfs(v); } } void Find_cc() { current_cc=0; for(int u=0;u<n;u++) { if(vis[u]) continue; current_cc++; dfs(u); } }
b.并查集(dsu)
int fa[maxn]; set<int> s;//统计连通分量个数 vector<int> v[maxn]; int m,n,x,y; void init() { for(int i=0;i<=n;i++) fa[i]=i; } int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);} void Union(int a,int b) { int A=Find(a),B=Find(b); if(A!=B) fa[A]=B; } void Find_cc() { init(); for(int i=0;i<m;i++) { scanf("%d%d",&x,&y); Union(x,y); } for(int i=1;i<=n;i++) { int k=Find(i); v[k].push_back(i); s.insert(k); } }
3.二分图判定
定义:对于无向图G=(V,E),可以把结点集分成不想交的两个部分X和Y=V-X,使得每条边的一个结点在X中,另一个在Y中
求法
//根据col的颜色判定 //col=0.未涂色。col=1表示涂黑色,col=2表示涂白色 //初始化根节点涂黑色 vector<int> G[maxn]; int col[maxn]; void init() { memset(col,0,sizeof(col)); col[root]=1; } bool bipartite(int u) { for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(col[v]==col[u]) return false; if(!col[v]) { col[v]=3-col[u]; if(!bipartite(v)) return false; } } return true; }
4.无向图的割顶和桥
a.割顶
考虑根节点和非根结点
根结点;对应直接子结点只有一个,不为割顶,否则为割点
非跟结点:在无向连通图G的DFS树中,非根结点u是G的割顶当且仅当u存在一个子结点v,使得v及其后代都没有反向边连回u的祖先(不算u),即low(v)>=pre(u)
b.桥
在求割顶的基础上改变个限制条件low(v)>pre(u),画个图就能明白
这也表明:边是桥,那么边连接的点一定是割顶,但反过来不成立,点是割顶,那么所连的边不一定是桥
求法
#include<bits/stdc++.h> using namespace std; const int maxn=10000; vector<int> G[maxn]; int pre[maxn],iscut[maxn]; int dfs_clock,n,m; 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]; if(!pre[v]) { child++; int lowv=dfs(v,u); lowu=min(lowv,lowu);//更新lowu是判断u的祖先有没可能是割顶 if(lowv>=pre[u]) iscut[u]=1; if(lowv>pre[u]) printf("边(%d, %d)是桥\n",u,v); } else if(pre[v]<pre[u]&&v!=fa) lowu=min(lowu,pre[v]);//1.v==fa表示这个fa-->u的反向边u-->fa,应该不予讨论,2.pre[v]<pre[u]表明有边连回u的祖先,应该更新 } if(fa<0&&child==1) iscut[u]=0;//根节点在上述的操作后一定变成割顶,即iscut[u]=1,需要根据根节点直接子结点的个数来判断 return lowu; } int main() { dfs_clock=0; scanf("%d%d",&n,&m); 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); } dfs(1,-1); for(int i=1;i<=n;i++) { if(iscut[i]) printf("割顶是:%d\n",i); } return 0; }
5.双连通分量
a.点双连通分量
定义:任意两点存在至少两条点不重复的路径.等价于任意两条边都在同一个简单环中,内部无割顶
求法
//根据内部无割顶,每次到达一个割顶,那么该割顶所在的环就是点双连通分量 int pre[maxn],iscut[maxn],bccno[maxn],dfs_clock,bcc_cnt;//bccno数组是来标记点双联通分量的点 vector<int> G[maxn],bcc[maxn]; stack<pair<int,int> > 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]; if(!pre[v]) { s.push({u,v});//注意是保存边,而不是点,因为割顶可能在多个点双连通分量.保存点就会使得某些点双连通分量缺点 child++; int lowv=dfs(v,u); lowu=min(lowu,lowv); if(lowv>=pre[u]) { iscut[u]=1; bcc_cnt++; bccno[bcc_cnt].clear(); while(1) { pair<int,int> 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.first==u&&x.second==v) break; } } } else if(pre[v]<pre[u]&&v!=fa) { s.push({u,v}); lowu=min(lowu,pre[v]); } } if(fa<0&&child==1) iscut[u]=0; return lowu; }
b.边双连通分量
定义:任意两点至少存在两条边不重复的路径,等价于每条边都至少在一个简单环中,即所有边都不是桥
求法
//根据定义:边双连通分量是没有公共结点的,所以第一次dfs求出桥。然后再一次dfs更新边连通分量 int pre[maxn],ebcno[maxn]; set<pair<int,int> > bridge; vector<pair<int,int> > ebc[amxn]; int dfs_clock,ebc_num; void dfs(int u,int fa) { int lowu=pre[u]=++dfs_clock; for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(!pre[v]) { int lowv=dfs(v,u); lowu=min(lowu,lowv); if(lowv>pre[u]) bridge.insert({u,v}); } else if(pre[v]<pre[u]&&v!=fa) lowu=min(lowu,pre[v]); } return lowu; } void dfs1(int u) { ebcno[u]=ebc_num; for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(ebcno[v]) continue; if(bridge.count({u,v})) continue; bec[ebc_num].push_back({u,v}); dfs1(v); } }
6.有向图的强连通分量
定义:类似无向图的连通分量
求法
vector<int> G[maxn]; int pre[maxn],lowlink[maxn],sccno[maxn];//lowlink[u]类似点双连通中的lowu.即u及其后代能追溯到的最早祖先的pre值 int dfs_clock,scc_cnt; stack<int> s;//直接保存点,因为没有强连通没有公共点 void dfs(int u) { lowlink[u]=pre[u]=++dfs_clock; s.push(u); for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(!pre[v]) { dfs(v); lowlink[u]=min(lowlink[u],lowlink[v]); } else if(!sccno[v]) lowlink[u]=min(lowlink[u],lowlink[v]); } if(lowlink[u]==pre[u]) { scc_cnt++; while(1) { int x=s.top();s.pop(); sccno[x]=scc_cnt; if(x==u) break; } } } void Find_scc(int n) { dfs_clock=scc_cnt=0; memset(sccno,0,sizeof(sccno)); memset(pre,0,sizeof(pre)); for(int i=0;i<n;i++) if(!pre[i]) dfs(i); }
连通图基本知识
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。