求解无向图的割点和桥

割点和桥的概念:

对于无向图G,如果删除某个点u后,连通分量数目增加(本来为一个连通集,现在变为多个),称u为图的割点。对于连通图,割点就是删除之后不再连通的点。当然了,桥也同理,只不过桥是边,也称为割边,如果删掉(u,v)之后连通分量数目增加,那么我们称(u,v)这条边为桥(割边)。

求割点和桥的方法:

①很容易想到的,暴力每个结点,然后用DFS判断连通分量是否增加,时间复杂度为O(n(n+m)),其中n和m分别是图中的点数和边数。

②深入挖掘DFS的性质,在线性时间O(n+m)时间内求出所有的割点和桥。

当然,效率来说当然选择第二种方法了,下面介绍第二种方法。

说明:

对于每一个顶点v,我们称其前序编号为pre[v],对于深度优先搜索生成树上的每个顶点v,计算编号最低的顶点,我们称之为Low(v),该点可从v开始通过树的零条或多条边,且可能还有一条后向边而达到。我们可以通过对该深度优先生成树执行一次后序遍历有效地算出Low,根据Low的定义可知,Low(v)是 ①pre(v) ②所有后向边(v,w)中最低的pre(w)。③树的所有边(v,w)中的最低Low(w)。对于任意一条边(v,w),只要检查pre(v)和pre(w)就可以知道它是一条树的边还是一条后向边。因此,Low(v)容易计算:我们只需扫描v的邻接表,应用适当的法则,并记住最小者。

割点满足的条件:

根是割点当且仅当它有多个儿子,因为如果有两个儿子,那么删除根结点后两个儿子就分别属于不同的子树上了,即产生了两个连通分量。如果根只有一个儿子,那么除去根不过是断离该根,对于其他任何的顶点v,当且仅当它某个儿子w使得Low(w) >= pre(v)才是割点。 

伪代码:

void Graph::findArt(Vertex v)
{
    v.visited = true;
    v.low = v.num = counter++; //Rule 1
    for each Vertex w adjacent to v  //遍历每一个邻接于v的顶点
    {
        if(!w.visited) //树边
        {
            w.parent = v; //父节点为v
            findArt(w);
            if(w.low >= v.num)
            {
                cout<<v<<" is cut point."<<endl;
                if(w.low > v.num)
                {
                    cout<<edge<<" is cut edge."<<endl;
                }
            }
            v.low = min(v.low,w.low); //Rule 3
        }
        else
        {
            if(v.parent != w) //后向边
            {
                v.low = min(v.low,w.num); //Rule 2
            }
        }
    }
}

c++实现代码:

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;

const int maxn = 1001;

vector<int> G[maxn];
int pre[maxn];
bool iscut[maxn];  //割点
bool bridge[maxn][maxn]; //桥(割边)
int low[maxn];
int n,m; //n个结点,m条两顶点彼此相连的边
int dfs_clock;
void init()
{
    int num1,num2;
    cout<<"total n vertexes:"<<endl;
    cin>>n;
    memset(iscut,false,sizeof(iscut));
    memset(pre,0,sizeof(pre));
    memset(low,0,sizeof(low));
    memset(bridge,false,sizeof(bridge));
    cout<<"total m edges which connect each other"<<endl;
    cin>>m;
    for(int i=0;i<m;i++)
    {
        cin>>num1>>num2;
        G[num1].push_back(num2);
        G[num2].push_back(num1);
    }
}
int dfs(int u,int fa)   //u在DFS树中的父结点是fa
{
    int lowu = pre[u] = ++dfs_clock;
    int child = 0;  //子结点数目
    for(int i=0;i<G[u].size();i++)
    {
        int v = G[u][i];
        if(!pre[v])  //没有访问过v,所以为树边
        {
            child++;  //子结点数目+1
            int lowv = dfs(v,u);
            lowu = min(lowu,lowv); //用后代的low函数更新u的low函数  (u,v)为树边
            if(lowv >= pre[u])  //非根结点u是G的割点,当且仅当u存在一个子结点v,使得v及其所有后代都没有反向边连回u的祖先
            {
                iscut[u] = true;  //u为割点
                if(lowv > pre[u])  //(u,v)为桥
                {
                    bridge[u][v] = true;
                }
            }
        }
        else if(pre[v] < pre[u] && v != fa){  //(u,v)为回边且v不为u的父结点,v为u的子结点,但是它指向的pre比pre[u]小的父亲
            lowu = min(lowu,pre[v]);  //用反向边更新u的low函数
        }
    }
    if(fa < 0 && child == 1) iscut[u] = 0;    //如果是根结点且儿子数只有一个,那么u不是割点
    low[u] = lowu;  //更新u的low值
    return lowu;
}
void display()
{
    for(int i=1;i<=n;i++)
    {
        if(iscut[i] == true)
        {
            cout<<i<<" is cut point."<<endl;
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(bridge[i][j] == true && i != j)
            {
                cout<<"("<<i<<","<<j<<")"<<" is bridge."<<endl;
            }
        }
    }
}
int main()
{
    //freopen("111","r",stdin);
    init();
    dfs_clock = 0;
    dfs(1,-1);
    display();
}

样例图:

输出结果:


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值