连通图基本知识

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了连通图基本知识相关的知识,希望对你有一定的参考价值。

看了LRJ的训练指南上连通有关的介绍,写得挺好,但是有些位置逻辑跳跃比较大,还有一些留给读者思考的位置,在此做个总结.

1.DFS框架

2.连通分量

3.二分图判定

4.无向图的割顶和桥

5.无向图的双连通分量

6.有向图的强连通分量(Tarjan算法)

 

1.DFS框架

连通图很多都是跟DFS框架里面的previsit()和posvisit()有关,不多说.

vector<int> G[maxn];
int vis[maxn];

void dfs(int u)
{
    vis[u]=1;
    previsit(u);
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(!vis[v]) dfs(v);
    }
    posvisit(u);
}

 

2.无向图的连通分量

定义:在无向图中,如果从结点u可以到达结点v,那么从结点u也必然可以到达u。

求法

a.建图+dfs

vector<int> G[maxn],cc[maxn];
int vis[maxn];
int current_cc;

void dfs(int u)
{
    vis[u]=1;
    cc[current_cc].push_back(u);
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(vis[v]) continue;
        dfs(v);
    }
}

void Find_cc()
{
    current_cc=0;
    for(int u=0;u<n;u++)
    {
        if(vis[u]) continue;
        current_cc++;
        dfs(u);
    }
}

b.并查集(dsu)

int fa[maxn];
set<int> s;//统计连通分量个数
vector<int> v[maxn];
int m,n,x,y;

void init()
{
    for(int i=0;i<=n;i++) fa[i]=i;
}

int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}

void Union(int a,int b)
{
    int A=Find(a),B=Find(b);
    if(A!=B) fa[A]=B;
}

void Find_cc()
{
    init();
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&x,&y);
        Union(x,y);
    }
    for(int i=1;i<=n;i++)
    {
        int k=Find(i);
        v[k].push_back(i);
        s.insert(k);
    }
}

 

3.二分图判定

定义:对于无向图G=(V,E),可以把结点集分成不想交的两个部分X和Y=V-X,使得每条边的一个结点在X中,另一个在Y中

求法

//根据col的颜色判定
//col=0.未涂色。col=1表示涂黑色,col=2表示涂白色
//初始化根节点涂黑色

vector<int> G[maxn];
int col[maxn];

void init()
{
    memset(col,0,sizeof(col));
    col[root]=1;
}

bool bipartite(int u)
{
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(col[v]==col[u]) return false;
        if(!col[v])
        {
            col[v]=3-col[u];
            if(!bipartite(v)) return false;
        }
    }
    return true;
}

 

4.无向图的割顶和桥

a.割顶

考虑根节点和非根结点

根结点;对应直接子结点只有一个,不为割顶,否则为割点

非跟结点:在无向连通图G的DFS树中,非根结点u是G的割顶当且仅当u存在一个子结点v,使得v及其后代都没有反向边连回u的祖先(不算u),即low(v)>=pre(u)

b.桥

在求割顶的基础上改变个限制条件low(v)>pre(u),画个图就能明白

 

这也表明:边是桥,那么边连接的点一定是割顶,但反过来不成立,点是割顶,那么所连的边不一定是桥

 

求法

#include<bits/stdc++.h>
using namespace std;
const int maxn=10000;

vector<int> G[maxn];
int pre[maxn],iscut[maxn];
int dfs_clock,n,m;


int dfs(int u,int 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])
        {
            child++;
            int lowv=dfs(v,u);
            lowu=min(lowv,lowu);//更新lowu是判断u的祖先有没可能是割顶
            if(lowv>=pre[u]) iscut[u]=1;
            if(lowv>pre[u])  printf("边(%d, %d)是桥\n",u,v);
        }
        else if(pre[v]<pre[u]&&v!=fa) lowu=min(lowu,pre[v]);//1.v==fa表示这个fa-->u的反向边u-->fa,应该不予讨论,2.pre[v]<pre[u]表明有边连回u的祖先,应该更新
    }
    if(fa<0&&child==1) iscut[u]=0;//根节点在上述的操作后一定变成割顶,即iscut[u]=1,需要根据根节点直接子结点的个数来判断
    return lowu;
}

int main()
{
    dfs_clock=0;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1,-1);
    for(int i=1;i<=n;i++)
    {
        if(iscut[i]) printf("割顶是:%d\n",i);
    }
    return 0;
}

 

5.双连通分量

a.点双连通分量

定义:任意两点存在至少两条点不重复的路径.等价于任意两条边都在同一个简单环中,内部无割顶

求法

//根据内部无割顶,每次到达一个割顶,那么该割顶所在的环就是点双连通分量

int pre[maxn],iscut[maxn],bccno[maxn],dfs_clock,bcc_cnt;//bccno数组是来标记点双联通分量的点
vector<int> G[maxn],bcc[maxn];

stack<pair<int,int> > s;//栈保存边

int dfs(int u,int 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])
        {
            s.push({u,v});//注意是保存边,而不是点,因为割顶可能在多个点双连通分量.保存点就会使得某些点双连通分量缺点
            child++;
            int lowv=dfs(v,u);
            lowu=min(lowu,lowv);
            if(lowv>=pre[u])
            {
                iscut[u]=1;
                bcc_cnt++;
                bccno[bcc_cnt].clear();
                while(1)
                {
                    pair<int,int> x=s.top();s.pop();
                    if(bccno[x.u]!=bcc_cnt)
                    {
                        bcc[bcc_cnt].push_back(x.u);
                        bccno[x.u]=bcc_cnt;
                    }
                    if(bccno[x.v]!=bcc_cnt)
                    {
                        bcc[bcc_cnt].push_back(x.v);
                        bccno[x.v]=bcc_cnt;
                    }
                    if(x.first==u&&x.second==v) break;
                }
            }
        }
        else if(pre[v]<pre[u]&&v!=fa) 
        {
            s.push({u,v});
            lowu=min(lowu,pre[v]);
        }
    }
    if(fa<0&&child==1) iscut[u]=0;
    return lowu;
}

 

b.边双连通分量

定义:任意两点至少存在两条边不重复的路径,等价于每条边都至少在一个简单环中,即所有边都不是桥

求法

//根据定义:边双连通分量是没有公共结点的,所以第一次dfs求出桥。然后再一次dfs更新边连通分量
int pre[maxn],ebcno[maxn];
set<pair<int,int> > bridge;
vector<pair<int,int> > ebc[amxn];
int dfs_clock,ebc_num;

void dfs(int u,int fa)
{
    int lowu=pre[u]=++dfs_clock;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(!pre[v])
        {
            int lowv=dfs(v,u);
            lowu=min(lowu,lowv);
            if(lowv>pre[u]) bridge.insert({u,v}); 
        }
        else if(pre[v]<pre[u]&&v!=fa) lowu=min(lowu,pre[v]);
    }
    return lowu;
}

void dfs1(int u)
{
    ebcno[u]=ebc_num;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(ebcno[v]) continue;
        if(bridge.count({u,v})) continue;
        bec[ebc_num].push_back({u,v});
        dfs1(v);
    }
}

 

6.有向图的强连通分量

定义:类似无向图的连通分量

求法

vector<int> G[maxn];
int pre[maxn],lowlink[maxn],sccno[maxn];//lowlink[u]类似点双连通中的lowu.即u及其后代能追溯到的最早祖先的pre值
int dfs_clock,scc_cnt;
stack<int> s;//直接保存点,因为没有强连通没有公共点


void dfs(int u)
{
    lowlink[u]=pre[u]=++dfs_clock;
    s.push(u);
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(!pre[v])
        {
            dfs(v);
            lowlink[u]=min(lowlink[u],lowlink[v]);
        }
        else if(!sccno[v]) lowlink[u]=min(lowlink[u],lowlink[v]);
    }
    if(lowlink[u]==pre[u])
    {
        scc_cnt++;
        while(1)
        {
            int x=s.top();s.pop();
            sccno[x]=scc_cnt;
            if(x==u) break;
        }
    }
}

void Find_scc(int n)
{
    dfs_clock=scc_cnt=0;
    memset(sccno,0,sizeof(sccno));
    memset(pre,0,sizeof(pre));
    for(int i=0;i<n;i++)
    if(!pre[i]) dfs(i);
}

 

以上是关于连通图基本知识的主要内容,如果未能解决你的问题,请参考以下文章

图神经网络基础 Graph 图以及python实现

(王道408考研数据结构)第六章图-第一节1:图的基本概念术语连通图连通分量和生成树森林

欧拉图基本概念

图查找排序

数据结构——图

数据结构—图图的定义图的存储结构