Tarjan求割点、强连通分量

以前学的全都忘了orz

前置知识:深度优先搜索树

以v为结点,对每个点进行dfs遍历,所得到的搜索树,即为深度优先搜索树

易知,对每个点仅遍历一次,故我们在该深度优先搜索树中加入”回退边”,来表示该节点与已遍历到的节点之间亦存在边

故我们在该树中存在两种边,一种是实际遍历到的边,一种是没有使用的回退边


何为割点?

给定一个连通无向图,其中任意一点被删除后,剩下的图仍连通,则称其为双连通图;若删除一些点后,剩下的图不连通,则称该点为割点or关节点

我们利用上述搜索树的想法,维护dfn[i]和low[i]两个数组,dfn表示该点被遍历到的次序(故用先序遍历),而low表示该点搜索子树(包含本身、包含回退边)的dfn最小值(故用后序遍历)

对于割点,在搜索树中,除了根节点外,若一个节点v的子树中存在一个节点u,其low[u]>=dfn[v],即u不存在指向v祖先的回退边,此时删除v,会使该节点与其他不再连通,故此时v为割点(根节点没有祖先,故特殊处理)

对于根节点,若其子树(在搜索树中两颗子树之间不相通)数量大于等于2,则此时删除根节点也会导致不连通,故根节点也为割点;否则根节点不是割点


给定有向图,强连通分量为一个点集,该点集中的点可以到达点集中的其他任意点(易知必定成环)

若整个图都为强连通分量,则称其为强连通图

故我们可以通过求强连通分量将有向有环图染色缩点,化为有向无环图

如何求?

  1. Tarjan算法,以dfs为基础,由强连通分量必定成环的特性,维护两个数组,dfn[i]与low[i],与一个栈stack,其中dfn为该点第一次被遍历到的时间次序,而low则是该点所能遍历到的点的dfn最小值,stack用来记录强连通分量中的点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    stack<int>s;
    int dfn[200005],low[200005];//初始置为0
    int col[200005];//用来染色
    vector<int>g[200005];//存图
    bool vis[200005];//表示点是否在栈中
    int col_num=0,tot=0;
    void tarjan(int x)
    {
    dfn[x]=low[x]=++tot;
    s.push(x);
    vis[x]=1;
    for(auto e:g[x])
    {
    if(!dfn[e])
    {
    tarjan(e);
    low[x]=min(low[x],low[e]);
    }else if(vis[e])
    {
    low[x]=min(low[x],low[e]);//对回退边进行统计
    }
    }
    if(dfn[x]==low[x])//本节点即为一个强连通分量的代表节点
    {
    col[x]=++col_num;
    vis[x]=0;
    while(s.top()!=x)//统计该分量内的点
    {
    vis[s.top()]=0;
    col[s.top()]=col_num;
    s.pop();
    }
    s.pop();
    }
    }
  2. Kosaraju算法,大致思路是将所有边反向,建一个反向图,对反向图进行dfs后得到dfs序,再对正向图根据dfs序列的反进行dfs,以消除单向边的影响

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    bool vis[200005];
    int q[200005];
    int n,tot=0;
    int fa[200005]
    vector<int> g1[200005],g2[200005];
    void dfs1(int x)
    {
    vis[x]=1;
    for(auto e:g[x])
    {
    if(!vis[e])
    {
    dfs1(e);
    }
    }
    q[++tot]=x;
    }
    void dfs2(int x,int y)
    {
    vis[x]=0;
    fa[x]=y;
    for(auto e:g[x])
    {
    if(vis[e])
    {
    dfs2(e,y);
    }
    }
    }
    int main()
    {
    for(int i=1;i<=n;i++) if(!vis[i]) dfs1(i);
    for(int i=n;i>=1;i--) if(vis[q[i]]) dfs2(q[i],q[i]);
    }

    本文内容大部分参考来源:

    https://www.cnblogs.com/llhthinker/p/4954082.html

    https://blog.csdn.net/li1615882553/article/details/79762771