今天我们接着搞图论:割点和割边
(一)割点
啥叫割点?
针对无向连通图,若删除一个点后使得该图不连通,则该点是割点。
注意:一个图中可能有多个割点
先上一组数据:
6 7
1 4
1 3
4 2
3 2
2 5
2 6
5 6
图是这样的:
很容易看出结果是:
2
那么如何求出图中的割点呢?
Algorithm1:dfs或bfs暴搜,不推荐也不想讲
Algorithm2:
我们可以从任意一个顶点开始遍历,用一个num数组来储存每个顶点是第几个访问到的。(有个专业术语叫时间戳)
上面一组数据的num是这样的:
1 2 3 4 5 6 num 1 3 2 4 5 6
我们在遍历所有点时会遇到割点(废话),主要是如何认定一个点是割点。假设访问到了k点,如果在没有访问过的点中,至少有一个点在不经过k点的情况下,无法回到已访问过的点,则k点是割点。(因为该图删除点k后不连通了)
算法核心:如何判断未被访问过的点u在不经过点k的情况下能否返回任何一个已访问过的点。
从树的角度来看,k是u的父亲,u是k的儿子,判断u能否不经过k而回到它的所有祖先。
我们用数组low来表示每个点在不经过父节点的前提下,能返回的最早的时间戳。
上面一组数据的low是这样的:
1 2 3 4 5 6
low 1 1 1 1 3 3
首先枚举k,再枚举跟k有边相连的u,如果存在low[u]>=num[k],即返回祖先必须经过k,则k是割点。
整个过程可以用dfs来实现。
下面呈上代码:

#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int n,m,e[1005][1005],root,num[1005],low[1005],flag[1005],index; void dfs(int cur,int dad) { int i,child=0; index++; num[cur]=index; low[cur]=index; for(i=1;i<=n;i++) { if(e[cur][i]==1) { if(num[i]==0) { child++; dfs(i,cur); low[cur]=min(low[cur],low[i]); if(cur!=root && low[i]>=num[cur]) { flag[cur]=1; } else if(cur==root && child==2) { flag[cur]=1; } } else if(low[i]!=dad) { low[cur]=min(low[cur],num[i]); } } } } int main() { int i,a,b,c; scanf("%d%d",&n,&m); memset(e,0,sizeof(e)); for(i=1;i<=m;i++) { scanf("%d%d",&a,&b); e[a][b]=1; e[b][a]=1; } root=1; dfs(1,root); return 0; }
有人可能发现,上面使用邻接矩阵来存图,这是不对的。因为这会导致该算法的时间复杂度为O(n^2)(跟暴搜差不多),算法就没有意义了。所以应该用邻接表来储存图,时间复杂度可以降到O(M+N)。
真正的代码:

#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int n,m,root,num[1005],low[1005],flag[1005],index; int a[2005],b[2005],next[2005],first[2005]; void dfs(int cur,int dad)//待判断顶点和它的父节点 { int i,child=0; index++;//index储存的是时间戳 num[cur]=index; low[cur]=index; for(i=first[cur];i!=-1;i=next[i])//邻接表优化 { if(num[b[i]]==0)//i是cur的儿子 { child++; dfs(b[i],cur);//继续遍历 low[cur]=min(low[cur],low[b[i]]);//更新low的值 if(cur!=root && low[b[i]]>=num[cur])//cur是割点 { flag[cur]=1; } else if(cur==root && child>=2)/*或者cur是祖先且它有两个儿子,那么它也是割点(删除它后两个儿子不连通) */ { flag[cur]=1; } } else if(b[i]!=dad)//i是cur的祖先,更新low { low[cur]=min(low[cur],num[b[i]]); } } } int main() { int i,x,y,c; scanf("%d%d",&n,&m); for(i=1;i<=n;i++) first[i]=-1; for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); a[i]=x; b[i]=y; } for(i=m+1;i<=2*m;i++)//注意是无向图哦 { a[i]=b[i-m]; b[i]=a[i-m]; } for(i=1;i<=2*m;i++) { next[i]=first[a[i]]; first[a[i]]=i; } root=1; dfs(1,root); for(i=1;i<=n;i++) { if(flag[i]==1) printf("%d ",i); } return 0; }
(二)割边
割边也成为桥,与割点类似
针对无向连通图,若删除一条边后使得该图不连通,则该边是割边。
同样的,一个图中可能有多条割边
同样先上一组数据:
6 6
1 4
1 3
4 2
3 2
2 5
5 6
图是这样的:
该图有两条割边:
5-6
2-5
割边的求法也与割点类似,只需将
if(low[i]>=num[cur])
改为:
if(low[i]>num[cur])
就行了,即该点到除了达不了它的任何祖先,也无法到达它的父亲。
好了,呈上代码,请注意输出部分:

#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int n,m,e[1005][1005],num[1005],low[1005]; int root,index; void dfs(int cur,int dad)//待判断顶点和它的父节点 { int i; index++;//index储存的是时间戳 num[cur]=low[cur]=index; for(i=1;i<=n;i++) { if(e[cur][i]==1) { if(num[i]==0)//i是cur的儿子 { dfs(i,cur);//继续遍历 low[cur]=min(low[cur],low[i]);//更新low的值 if(low[i]>num[cur]) printf("%d-%d\n",cur,i); } else if(i!=dad)//i是cur的祖先,更新low low[cur]=min(low[cur],num[i]); } } } int main() { int i,a,b; scanf("%d%d",&n,&m); for(i=1;i<=m;i++)//注意是无向图哦 { scanf("%d%d",&a,&b); e[a][b]=1; e[b][a]=1; } root=1; dfs(1,root); return 0; }
同割点一样,割边算法也应该用邻接表来存图,否则该算法就无意义了。使用邻接表后,这个算法的时间复杂度也是O(M+N)。
真.代码如下:

#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int n,m,a[1005],b[1005],first[1005],next[1005],num[1005],low[1005]; int root,index; void dfs(int cur,int dad)//待判断顶点和它的父节点 { int i; index++;//index储存的是时间戳 num[cur]=low[cur]=index; for(i=first[cur];i;i=next[i]) { if(num[b[i]]==0)//i是cur的儿子 { dfs(b[i],cur);//继续遍历 low[cur]=min(low[cur],low[b[i]]);//更新low的值 if(low[b[i]]>num[cur]) printf("%d-%d\n",cur,b[i]); } else if(b[i]!=dad)//i是cur的祖先,更新low low[cur]=min(low[cur],num[b[i]]); } } int main() { int i,x,y; scanf("%d%d",&n,&m); for(i=1;i<=m;i++)//注意是无向图哦 { scanf("%d%d",&x,&y); a[i]=x; b[i]=y; } for(i=m+1;i<=2*m;i++) { a[i]=b[i-m]; b[i]=a[i-m]; } for(i=1;i<=m*2;i++) { next[i]=first[a[i]]; first[a[i]]=i; } root=1; dfs(1,root); return 0; }
~祝大家编程顺利~