给出一幅图:
现在需要去掉一个点,使得全图不再连通,如何完成?
通过观察图可以发现在除去顶点2后,定点5,6无法连通,故顶点2是本图的割点。
如何完成这一算法呢?我们结合深度搜索来实现。首先引入一个概念——时间戳。时间戳就是记录顶点在遍历时第几个被访问,例如上图我们定义num[]数组记录时间戳,以1为起点开始,则num[1]=1;接下来是顶点3,故num[2]=3,即3号顶点时间戳为2。以此类推,3后面是2,然后是4,之后是5,随后是6.最终num[]:
num 1 2 3 4 5 6
1 3 2 4 5 6
接下来呢?如何知道一个顶点被除去后图是否连通?方法就是看他后面还未经过的顶点(看做被除去顶点的儿子顶点)是否能在不经过他的情况下回到被除去顶点之前的点。此时我们定义low[]数组,记录顶点i在不经过父顶点的情况下能回到的最小的时间戳。
例如:顶点u有一个子顶点v,若low[v]>=num[u],则v不能在不经过u的情况下到达祖先顶点,即说明顶点u是割点。若low[v]<num[u],则v能在不经过u的情况下到达祖先顶点,说明u不是割点。以本图来看,若除去顶点2,则5,6能回到最小的时间戳为3==num[2],故2为本图的割点。若除去顶点5,其儿子顶点6可在不经过5的情况下经过定点2回到祖先顶点,即说明5非割点。
完整的代码实现如下:
#include <iostream>
using namespace std;
int n,m,e[1000][1000],root;
int num[1000],low[1000],flag[1000],index;//index记录时间戳的递增
int min(int a,int b)
{
return a<b?a:b;
}
//算法核心
void dfs(int cur,int father)//传入当前顶点及其父定点编号
{
int child=0,i;//child记录当前顶点子顶点的个数
index++;
num[cur]=index;
low[cur]=index;
for(i=1;i<=n;i++)
{
if(e[cur][i]==1)
{
if(num[i]==0)//时间戳为0说明未被访问过
{
child++;
dfs(i,cur);//继续深搜遍历找儿子
//更新cur能回到最早的时间戳
low[cur]=min(low[cur],low[i]);
//当前节点low[i]>=num[cur];即为割点
if(cur!=root&&low[i]>=num[cur])
flag[cur]=1;
//若果当前顶点是根节点,生成树中根节点必须要有两个儿子,那么这个根节点才是割点
if(cur==root&&child==2)
flag[cur]=1;
}
//i被访问过,且i不是当前顶点cur的父亲,则需要更新当前顶点cur能访问到的最早的时间戳
else if(i!=father)
{
low[cur]=min(low[cur],num[i]);
}
}
}
}
int main()
{
int u,v;
while(cin>>n>>m)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
e[i][j]=0;
for(int i=1;i<=m;i++)
{
cin>>u>>v;
e[u][v]=1;e[v][u]=1;
}
root=1;
dfs(1,root);
for(int i=1;i<=n;i++)
{
if(flag[i]==1) cout<<i<<" ";
}
cout<<endl;
}
return 0;
}