Tarjan Algorithm

 


List

Knowledge

基本知识

Tarjan 主要是用来求有向图的强连通分量(缩点)和无向图的桥和割顶。首先都是要求出 DFN 和 LOW 值:DFN 是指一个点被搜索的次序编号,LOW 是指一个点的子树中最小编号。

基本概念

  1. 割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点。
  2. 割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。
  3. 点连通度:最小割点集合中的顶点数。
  4. 割边(桥):删掉它之后,图必然会分裂为两个或两个以上的子图。
  5. 割边集合:如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合。
  6. 边连通度:一个图的边连通度的定义为,最小割边集合中的边数。
  7. 缩点:把没有割边的连通子图缩为一个点,此时满足任意两点之间都有两条路径可达。
    注:求块<>求缩点。缩点后变成一棵k个点k-1条割边连接成的树。而割点可以存在于多个块中。
  8. 双连通分量:分为点双连通和边双连通。它的标准定义为:点连通度大于1的图称为点双连通图,边连通度大于1的图称为边双连通图。通俗地讲,满足任意两点之间,能通过两条或两条以上没有任何重复边的路到达的图称为双连通图。无向图G的极大双连通子图称为双连通分量。

复杂度

O(n+m)

有向图

首先搜索一个点,赋 DFN 和 LOW 初值为它的 DFN,把它丢进栈里,然后
遍历它的每个相邻点:
if 如果该点没被搜到过
then 搜索该点 然后用该点的 LOW 更新现在点的 LOW
else if 下个点还在栈里
then 用下个点的 DFN/LOW 更新这个点的 LOW (为什么 DFN 和 LOW 都可以:因为它在栈中说明它还不是强连通分量,所以 LOW 还没被更新)
最后判断一下它的 LOW 值和 DFN 值是否还相等,如果相等,说明有两种情况:
1.它没有出边
2.它的子节点最终回到了它
两种情况都说明以它为根的树为一个强连通分量。这个时候就只要不断退栈到这个点为止,退出来的所有元素为一个强连通分量。

Code

struct Tarjan{
    static const int N=500010,M=500010;
    int n,m,tot,_clock,scc;
    int ne[M],to[M];bool in[N];
    int fr[N],low[N],dfn[N],f[N],st[N];
    inline void add(int u,int v){
        to[++tot]=v;ne[tot]=fr[u];fr[u]=tot;
    }
    void Init(){
        n=gi(),m=gi();
        for(int i=1;i<=m;i++){
            int u=gi(),v=gi();add(u,v);
        }
    }
    inline void dfs(int x){
        dfn[x]=low[x]=++_clock;
        st[++tot]=x;in[x]=true;
        for(int o=fr[x];o;o=ne[o])
            if(!dfn[to[o]])dfs(to[o]),low[x]=min(low[x],low[to[o]]);
            else if(in[to[o]])low[x]=min(low[x],low[to[o]]);
        if(dfn[x]==low[x]){
            scc++;
            while(st[tot]!=x){
                f[st[tot]]=scc;in[st[tot]]=false;tot--;
            }
            f[x]=scc;in[x]=false;tot--;
        }
    }
    void Work(){
        _clock=tot=scc=0;
        memset(dfn,0,sizeof(dfn));
        memset(in,false,sizeof(in));
        for(int i=1;i<=n;i++)
            if(!dfn[i])dfs(i);
    }
}Tar;

  

缩点

上面代码中f[]代表没个点属于那个块,只需把每一块缩成一个点即可,如果两个块中的点有相连,那么将这个块连起来

Code

//在上面结构体中加上这几行就行
vector<int>p[N]
void Rebuild(){
    for(int i=1;i<=n;i++)
        for(int o=fr[i];o;o=ne[o]){
            if(f[i]==f[to[o]])continue;
            p[f[i]].push_back(f[to[o]]);
        }    
}

  

用途

缩完点非常好,dfs随便跑,有很多方面应用,比如下面Practice的第一题
这个图会变得非常优美,有向无环图→DAG
DAG

无向图

在无向图中因为一条边可以来回走,所以要保证不能用父亲更新,但是直接
记父亲又不行,因为可能会有重边,所以就要记录边的编号。更新的过程还是一
样 的 。

Articulation Point-割顶与连通度

在无向连通图中,删除一个顶点v及其相连的边后,原图从一个连通分量变成了两个或多个连通分量,则称顶点v为割点,同时也称关节点(Articulation Point)。一个没有关节点的连通图称为重连通图(biconnected graph)。若在连通图上至少删去k 个顶点才能破坏图的连通性,则称此图的连通度为k。

关节点和重连通图在实际中较多应用。显然,一个表示通信网络的图的连通度越高,其系统越可靠,无论是哪一个站点出现故障或遭到外界破坏,都不影响系统的正常工作;又如,一个航空网若是重连通的,则当某条航线因天气等某种原因关闭时,旅客仍可从别的航线绕道而行;再如,若将大规模的集成电路的关键线路设计成重连通的话,则在某些元件失效的情况下,整个片子的功能不受影响,反之,在战争中,若要摧毁敌方的运输线,仅需破坏其运输网中的关节点即可。

割顶求法
1. 对根节点u,若其有两棵或两棵以上的子树,则该根结点u为割点;
2. 若low[v]>=dfn[u],则u为割点,u和它的子孙形成一个块。因为这说明u的子孙不能够通过其他边到达u的祖先,这样去掉u之后,图必然分裂为两个子图。Analysis:对非叶子节点u(非根节点),若其子树的节点均没有指向u的祖先节点的回边,说明删除u之后,根结点与u的子树的节点不再连通;则节点u为割点。

Code

struct ArticulationPoint{
    static const int N=500010,M=500010;
    int n,m,tot,_clock;
    int ne[M],to[M];bool cut[N];
    int fr[N],low[N],dfn[N],father[N];
    inline void add(int u,int v){
        to[++tot]=v;ne[tot]=fr[u];fr[u]=tot;
    }
    void Init(){
        n=gi(),m=gi();
        for(int i=1;i<=m;i++){
            int u=gi(),v=gi();add(u,v);add(v,u);
        }
    }
    inline void Tarjan(int x,int fa){
        dfn[x]=low[x]=++_clock;father[x]=fa;
        for(int o=fr[x];o;o=ne[o])
            if(!dfn[to[o]]){
                Tarjan(to[o],x);
                low[x]=min(low[x],low[to[o]]);
            }
            else if(to[o]>>1!=fa>>1)low[x]=min(low[x],low[to[o]]);
    }
    void Work(){
        _clock=0;
        memset(dfn,0,sizeof(dfn));
        for(int i=1;i<=n;i++)
            if(!dfn[i])Tarjan(i,-1);
        tot=0;int k=0;//这里1为根节点
        for(int i=2;i<=n;i++)
            if(father[i]==1)tot++;
            else if(low[i]>=dfn[father[i]])cut[father[i]]=1,++k;
        if(tot>1)cut[1]=true,++k;
        printf("%d\n",k);//割顶个数
        for(int i=1;i<=n;i++)if(cut[i])printf("%d ",i);
    }
}Tar;

  

Bridge-桥

桥 的 判 定 方 法 是 , 如 果 DFN(u) < LOW(v) 则 边 (u,v) 为 桥 。( 因 为
DFN(u) < LOW(v)说明 v 没有回到 u 之前的节点,即 u 和 v 不是一个块(双连通
分量,顾名思义要有两条路)的。)

Code

struct Bridge{
    static const int N=500010,M=500010;
    int n,m,tot,_clock,ans;//ans 统计桥的个数
    int ne[M],to[M];
    int fr[N],low[N],dfn[N];
    inline void add(int u,int v){
        to[++tot]=v;ne[tot]=fr[u];fr[u]=tot;
    }
    void Init(){
        n=gi(),m=gi();
        for(int i=1;i<=m;i++){
            int u=gi(),v=gi();add(u,v);add(v,u);
        }
    }
    inline void Tarjan(int x,int fa){
        dfn[x]=low[x]=++_clock;
        for(int o=fr[x];o;o=ne[o])
            if(!dfn[to[o]]){
                Tarjan(to[o],x);
                low[x]=min(low[x],low[to[o]]);
                if(low[to[o]]>dfn[x])ans++;
            }
            else if(to[o]>>1!=fa>>1)low[x]=min(low[x],low[to[o]]);//重边不为桥
    }
    void Work(){
        _clock=ans=0;
        memset(dfn,0,sizeof(dfn));
        for(int i=1;i<=n;i++)
            if(!dfn[i])Tarjan(i,-1);
        printf("%d",ans);
    }
}Tar;

  

一些有用的定理

  1. 有向无环图中唯一出度为0的点,一定可以由任何点出发均可达(由于无环,所以从任何点出发往前走,必然终止于一个出度为0的点)
  2. 有向无环图中所有入度不为0的点,一定可以由某个入度为0的点出发可达。(由于无环,所以从任何入度不为0的点往回走,必然终止于一个入度为0的点)

Practice

CSDN-Bzoj1179 Apio2009 Atm(2016-09-20 19:54)

Cnblogs-Bzoj1179 Apio2009 Atm(2016-09-20 19:54)

 

 

posted @ 2016-09-20 21:06  _Mashiro  阅读(388)  评论(0编辑  收藏  举报