【学习笔记】图论 割点 割边

算法介绍

Tarjan_割点

  • 适用范围:无向图
  • 功能:给定无向图G=(V,E),求出一个点集合V’,包含图中所有的割点。
  • 时间复杂度:O(N+E),N为图中点数,E为图中边数。

Tarjan_桥

  • 适用范围:无向图
  • 功能:给定无向图G=(V,E),求出一个边集合E’,包含图中所有的桥。
  • 时间复杂度:O(N+E),N为图中点数,E为图中边数。

算法讲解

一些概念:

  1. 点连通度:去掉最少的点使得图分为若干联通分支。只有点连通度为1的图有割点.
  2. 割点:若去掉某个节点将会使图分为若干联通分支,则改点称为割点。
  3. 边连通度:去掉最少的边使得图分为若干联通分支。只有边连通度为1的图有桥。
  4. 桥:若去掉某条边将会使图分为若干联通分支,则改边称为桥。

现实意义:

通信网络中,用来衡量系统可靠性,连通度越高,可靠性越高。

割点

  1. 暴力求解,依次删除每一个节点v,用DFS(或者BFS)判断是否连通,再把节点加入图中。若用邻接表(adjacency list),需要做V次DFS,时间复杂度为O(V∗(V+E))。
    这个算法复杂度太高,我们需要去改进它,我们想:能否一遍DFS求解?
  2. Tarjan算法
    我们不难发现:一个顶点u是割点,当且仅当满足(1)或(2)
    (1) u为根节点,且u有多于一个子树。
    (2) u为非根节点,u有一个子节点s,且没有任何从s或s的后代节点指向v的真祖先的后向边。
    对于根节点我们可以进行特判,那么非根节点我们如何处理呢?
    思路:
    我们定义DNF[v]为v结点的入时间戳,即根据dfs序给节点进行编号,定义LOW[v]为v及v的子孙所能达到的最小节点的DFN,那么判定v是否是节点就很方便了,只要u有一个儿子v,使得DNF[u]< =LOW[v],则u是割点。

桥:

  1. 思路和求割点一样,也需要dfn数组和low数组辅助,只是不用判根,(u,v)是桥当且仅当DFN[u]< low[v],因为若u有一个子节点v,v及它的子孙所能到达的节点不超过v,及无法到u以上,那么这条树边就是桥了。
  2. 需要注意的是重边情况,若有两条边(1,2),(2,1),那么都不是桥但是若只有一条(1,2),则是桥。但是在处理的时候若只按照(v!=fa)判断,这两条边只算了一条,我们需要的是不重复计算同一条边 ,那么如何判断是不是同一条边呢?链式前向星为我们提供了一种方法,因为边存储时同一条边的序号是(1,2),(3,4),……这样下去的,若((i+1)/2==(j+1)/2)则i,j是同一条边,这样就判断出来了。

CODE

割点模板:

// luogu P3388
#include
using namespace std;
const int MAXN=100010,MAXM=100010;
int Head[MAXN],Next[MAXM*2],To[MAXM*2];
bool vis[MAXN],cutv[MAXN];
int dfn[MAXN],low[MAXN];
int n,m,tot,tim,root,rootson;

void add_eage(int x,int y)
{
    tot++;
    Next[tot]=Head[x];
    Head[x]=tot;
    To[tot]=y;
}
void ReadInfo()
{
    scanf("%d%d",&n,&m);
    tim=tot=0;
    for (int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add_eage(x,y); add_eage(y,x);
    }
}
void Tarjan(int u,int pre)
{
    dfn[u]=low[u]=++tim;
    vis[u]=true;
    for (int i=Head[u];i;i=Next[i])
    {
        int v=To[i];
        if (!vis[v])
        {
            Tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if (u!=root && dfn[u]<=low[v]) cutv[u]=true;
            else if (u==root && ++rootson==2) cutv[u]=true;
        }
        else if (v!=pre) low[u]=min(low[u],dfn[v]);     
    }
}
void solve()
{
    memset(dfn,-1,sizeof(dfn));
    memset(low,-1,sizeof(low));
    memset(vis,false,sizeof(vis));
    memset(cutv,false,sizeof(cutv));
    for (int i=1;i<=n;i++)
        if (!vis[i])
        {
            root=i;
            rootson=0;
            Tarjan(i,0);
        }
}
void Outit()
{
    int num=0;
    for (int i=1;i<=n;i++)
        if (cutv[i]) num++;
    printf("%d\n",num);
    for (int i=1;i<=n;i++)
        if (cutv[i]) printf("%d ",i);
    printf("\n");
}

int main()
{
    ReadInfo();
    solve();
    Outit();    
    return 0;
}

桥模板(可处理重边):

#include
using namespace std;
const int MAXN=100010,MAXM=100010;
int Head[MAXN],Next[MAXM*2],To[MAXM*2];
bool vis[MAXN];
int dfn[MAXN],low[MAXN];
int n,m,tot,tim,num_cutedge;
struct Edge{
    int u,v;
}cutedge[MAXM];

void add_eage(int x,int y)
{
    tot++;
    Next[tot]=Head[x];
    Head[x]=tot;
    To[tot]=y;
}
void ReadInfo()
{
    memset(Head,0,sizeof(Head));
    scanf("%d%d",&n,&m);
    tim=tot=0;
    for (int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add_eage(x,y); add_eage(y,x);
    }
}
void Tarjan(int u,int id)
{
    dfn[u]=low[u]=++tim;
    vis[u]=true;
    for (int i=Head[u];i;i=Next[i])
    {
        int v=To[i];
        if (!vis[v])
        {
            Tarjan(v,i);
            low[u]=min(low[u],low[v]);
            if (dfn[u]else if ((i+1)/2!=(id+1)/2) low[u]=min(low[u],dfn[v]);      
    }
}
void solve()
{
    memset(dfn,-1,sizeof(dfn));
    memset(low,-1,sizeof(low));
    memset(vis,false,sizeof(vis));
    num_cutedge=0;
    for (int i=1;i<=n;i++)
        if (!vis[i]) Tarjan(i,0);
}
void Outit()
{
    printf("the number of the bridges is %d\n",num_cutedge);
    for (int i=1;i<=num_cutedge;i++)
        printf("%d %d\n",cutedge[i].u,cutedge[i].v);
}

int main()
{
    ReadInfo();
    solve();
    Outit();    
    return 0;
}

你可能感兴趣的:(图论,学习笔记)