求解无向图的割点和桥

来源:互联网 发布:组三组六计划软件 编辑:程序博客网 时间:2024/04/29 07:27

割点和桥的概念:

对于无向图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 0
原创粉丝点击