中级篇——图的最小割点

给出一幅图:

现在需要去掉一个点,使得全图不再连通,如何完成?

通过观察图可以发现在除去顶点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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值