参考啊哈算法第八章第三节,第四节:
先介绍图的割点算法
采用两种存储方法:
1.数组存储
2.图的邻接表法
第一种方法:
#include
int min(int a,int b)
{
return aint n,m,e[9][9];
int root,num[9],low[9],flag[9],index;//index用来进行时间戳的递增
//割点算法核心
void dfs(int cur,int father)
{
int child=0,i; //child用来记录生成树中当前顶点cur的儿子数量
index++;
num[cur]=index;//当前cur的时间戳
low[cur]=index;//当前cur能访问到最早的时间戳。刚开始肯定是自己啦。
for(i=1;i<=n;i++)//枚举与当前顶点cur有边相连的顶点i
{
if(e[cur][i]==1)
{
if(num[i]==0)//如果当前顶点的时间戳为零说明当前顶点还没有被访问过
{
child++;
dfs(i,cur);//继续往下深度优先遍历搜索,此时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曾经被访问过,并且这个顶点不是当前顶点cur的父亲。则说明i为cur的祖先,
//因为i不可能为cur的儿子,如果为儿子,在第一个if语句中就判断了,因此需要更新当前节点cur能访问到最早节点的时间戳
//自反的相邻边不满足。
else if(i!=father)
low[cur]=min(low[cur],num[i]);
}
}
return;
}
int main()
{
int x,y;
scanf("%d%d",&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++)
{
scanf("%d%d",&x,&y);
e[x][y]=1;
e[y][x]=1;
}
root=1;
dfs(1,root);//从1号节点开始进行深度优先遍历
for(int i=1;i<=n;i++)
if(flag[i])
printf("%d ",i);
return 0;
}
对for循环里面的做一些改变
if(i==cur||i==father||!e[cur][i]) continue;
//有儿子并且判断儿子是否为祖宗。
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;
if(cur==root&&child==2)
flag[cur]=1;
}
else low[cur]=min(low[cur],num[i]);
采用图的邻接表存储法:
#include
int u[15],v[15],w[15];
int first[7],next[15];
int min(int a,int b)
{
return aint n,m,e[9][9];
int root,num[9],low[9],flag[9],index;//index用来进行时间戳的递增
//割点算法核心
void dfs(int cur,int father)
{
int child=0; //child用来记录生成树中当前顶点cur的儿子数量
index++;
num[cur]=index;//当前cur的时间戳
low[cur]=index;//当前cur能访问到最早的时间戳。刚开始肯定是自己啦。
//枚举与当前顶点cur有边相连的顶点i
int k=first[cur];
while(k!=-1)
{
if(w[k]==1)
{
if(num[v[k]]==0)//如果当前顶点的时间戳为零说明当前顶点还没有被访问过
{
child++;
dfs(v[k],cur);//继续往下深度优先遍历搜索,此时i为cur的儿子
//更新当前顶点cur能访问到最早定点的时间戳,因为如果他的儿子比他访问的还早,那么他肯定不需要通过父亲就能访问到
low[cur]=min(low[cur],low[v[k]]);
//如果当前节点不是根节点并且满足low[i]>=num[cur],则当前顶点为割点
if(cur!=root&&low[v[k]]>=num[cur])
flag[cur]=1;
if(cur==root&&child==2)
flag[cur]=1;
}
//如果当前顶点i曾经被访问过,并且这个顶点不是当前顶点cur的父亲。则说明i为cur的祖先,
//因为i不可能为cur的儿子,如果为儿子,在第一个if语句中就判断了,因此需要更新当前节点cur能访问到最早节点的时间戳
else if(v[k]!=father)
low[cur]=min(low[cur],num[v[k]]);
}
k=next[k];
}
return;
}
int main()
{
int x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u[i],&v[i]);
w[i]=1;
}
for(int i=m+1;i<=2*m;i++)
{
u[i]=v[i-m];
v[i]=u[i-m];
w[i]=1;
}
for(int i=1;i<=n;i++)
first[i]=-1;
for(int i=1;i<=2*m;i++)
{
next[i]=first[u[i]];
first[u[i]]=i;
}
root=1;
dfs(1,root);//从1号节点开始进行深度优先遍历
for(int i=1;i<=n;i++)
if(flag[i])
printf("%d ",i);
return 0;
}
/*
6 7
1 4
1 3
4 2
3 2
2 5
2 6
5 6
*/
图的割边算法:
只需要将上述程序改变一个条件
low[v]>=num[u]
改为
low[v]>num[u]
因为low[v]>=num[u]代表点v不可能在不经过父节点回到祖先的(包括父亲),所以顶点u是割点,如果low[v]>num[u]则表示连父亲都回不到了,倘若顶点v不能回到祖先,也没有另外一条边能回到父亲,那么u-v这条边就是割边,代码如下(只对上边做做一些修改):
if(num[i]==0)
{
dfs(i,cur);
low[cur]=min(low[cur],low[i]);
if(low[i]>num[cur])
printf("%d-%d\n",cur,i);
}
else if(i!=father)
low[cur]=min(low[cur],num[i]);
if(num[v[k]]==0)
{
dfs(v[k],cur);
low[cur]=min(low[cur],low[v[k]]);
if(low[v[k]]>num[cur])
printf("%d-%d\n",cur,v[k]);
}
else if(v[k]!=father)
low[cur]=min(low[cur],num[v[k]]);