• 浅谈图论(二)——割点与割边


    今天我们接着搞图论:割点和割边

    (一)割点

    啥叫割点?

    针对无向连通图,若删除一个点后使得该图不连通,则该点是割点。

    注意:一个图中可能有多个割点

    先上一组数据:

     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
    ",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
    ",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;
    }
    割边

    ~祝大家编程顺利~

  • 相关阅读:
    【URL重写】IIS7配置URL重写
    【IIS7.5】Asp文件上传限制,加载页面大小限制
    msxml3.dll 错误 '800c0005' 系统错误: -2146697211。
    【转】修改3389远程端口的批处理文件.bat
    第一篇:无角牛MVC通用后台数据库设计
    无角牛MVC通用后台
    个人收集资料整理-WebForm
    个人收集资料整理-WinForm
    win7系统中桌面图标显示不正常问题
    ASP.NET MVC 第六回 过滤器Filter
  • 原文地址:https://www.cnblogs.com/llllllpppppp/p/7593126.html
Copyright © 2020-2023  润新知