无向图的割点

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tengfei461807914/article/details/82026701

概念:

割点:有一个无向图G,如果删除某个顶点u以后,联通分量的数目增加,称u为图的关节点,或割点。

祖先:树形结构的概念,从根到该节点缩经分支上的所有节点。

子孙(后代):树形结构的概念,以某节点为根的紫书中的任一节点都成为该节点的子孙(后代)。

DFS森林(深度优先生成树): 从图中某一顶点出发,利用深度优先搜索遍历整个图,得到的包含树边回边(反向边)的一颗树形结构。

树边:深度优先森林G‘中的边,如果顶点v是在寻找边(u,v)时候首次被发现的,那么(u,v)就是一条树边。G5中所有实线的边。

回边(反向边):深度优先森林G‘中的边,连接顶点u到它的某一祖先顶点v的边。G5中所有虚线的边。

图1:无向图G5
这里写图片描述

图2:G5的深度优先生成树
这里写图片描述

定理:

无向图割点的存在条件:

从图2中可以看出,DFS树中的割点有两类特性:

  1. 若生成树的根有两颗或者两颗以上的子树,则此根为割点。例如顶点A,删除以后生成2颗以上的子树。
  2. 若DFS树种某个非叶子顶点v,其某颗子树的根和子树中的其他节点均没有指向v的祖先的回边,则v为割点。例如,顶点B、D、G。

在无向连通图G的DFS树中,非根节点u是G的割点当且仅当u存在一个子节点v,使得v及其所有后代都没有反向边连回u的祖先(连回u不算)。

证明:如下图,考虑u的任意子节点v。如果v及其后代不能连回f,则删除u之后,f和v不再联通;反过来,如果v或它的某一个后代存在一条反向边连回f,则删除u后,以v根的整颗子树中的所有节点都可以利用这条反向边与f连通。如果所有子树中的节点都和f连通,根据“连通”关系的传递性,整个图就是连通的。
这里写图片描述

换句话说,low(v)表示v的子孙和v本身是否存在一条边能够连接回u,由于使用时间戳标记了dfs树,那么先被遍历的节点时间戳一定小于后被遍历的节点。图中u节点的时间戳就一定小于v和v的子孙的时间戳,时间戳存储在pre中。

那么,low中记录的值就可以用时间戳来表示,如果low(v)中存在一个时间戳小于u节点,那么就相当于v或者v的子节点中有一条边连到了u节点的祖先节点上,就像上图中的右侧。此时即使将u节点拿掉,也会有一条边从v或者v的子孙连到f上,并不影响连通性。

同理,如果low(v)大于等于pre(u),那么v节点或者v的子孙一定是有一条(这条边记录的是连接pre值最小的,也就是最靠近根节点的某个节点)边连接到了u的子孙当中,说明去掉u以后,v和v的子孙会形成一个联通分量。

代码:

下面数据对应图G,表示有13个顶点,16条边

13 16
a b
a c
a l
a f
b d
d e
c b
l m
m b
i g
g k
g h
g b
j m
j l
h b

#include<bits/stdc++.h>
using namespace std;

const int maxn=1001;//顶点数

int V,E;//顶点数和边数

vector<int> G[maxn];//邻接表
int is_cut[maxn];//每个顶点是否为割点,是1,不是0
int pre[maxn],dfs_clock;//每个节点的时间戳,时间戳记录

int low[maxn];//u及其后代所能连回的最招的祖先的pre值




int cut_vertex(int u,int father)
{
    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])
        {
            child++;
            int lowv=cut_vertex(v,u);
            lowu = min(lowu,lowv);//更新u节点保存的low(u)为u节点及其子孙节点之中pre值最小的
            if(lowv>=pre[u])//如果u的某个子节点中v的low值大于等于当前节点u的时间戳,说明有边连到了u的子孙节点上
            {
                is_cut[u]=true;
            }
        }
        else
        {
            /*
            由于存储的是无向图的邻接表,如果有边a - b
            那么G[a]={b},且G[b]={a}
            v!=father就是为了防止a遍历了b以后反过来b遍历a
            */
            if(pre[v]<pre[u]&&v!=father)
            {
                lowu=min(lowu,pre[v]);
            }
        }
    }
    if(father<0&&child==1)//根节点,而且只有一个子节点
    {
        is_cut[u]=0;
    }
    low[u]=lowu;
    return lowu;
}
/*
A到K分别用 0到12表示

数据16条边13个点 对应图G
13 16
a b
a c
a l
a f
b d
d e
c b
l m
m b
i g
g k
g h
g b
j m
j l
h b

*/


int main()
{
    ios::sync_with_stdio(false);
    V=13;
    E=16;
    dfs_clock=0;
    memset(pre,0,sizeof(pre));


    int f,t;
    char a,b;
    for(int i=1;i<=E;i++)
    {
        cin>>a>>b;
        f=a-'a';
        t=b-'a';
        G[f].push_back(t);//无向图
        G[t].push_back(f);
    }

    cut_vertex(0,-1);
    for(int i=0;i<13;i++)//13个顶点,是割点的输出
    {
        if(is_cut[i])
            cout<<(char)(i+'a')<<" ";
    }

    cout<<endl;
    return 0;
}


这里写图片描述

参考内容

刘汝佳的大白书
数据结构 严蔚敏

猜你喜欢

转载自blog.csdn.net/tengfei461807914/article/details/82026701
今日推荐