求有向图强连通分量的两个算法


tarjan算法、Kosaraju算法——求有向图强连通分量

Tarjan算法

Tarjan算法是一种基于DFS的算法,使用栈来存储访问过程中的顶点

先引入两个非常重要的数组:dfnlow

dfn:顶点被搜索到的“时间戳”,用于记录顶点被搜到的次序,一个顶点的时间戳记录后就不再改变

low:在本次DFS搜索中(最外层循环),且仍在栈中的最小时间戳,像是确立了一个关系,low相等的点在同一强连通分量中。

注意初始化时 dfn= low = ++cnt

算法思路:

图不一定是强连通图,所以跑Tarjan时要枚举每个点,若dfn == 0,进行深搜。

对于搜到的顶点,寻找以其为弧尾的顶点,判断这些顶点是否已经被搜索过,若没有(显然也一定没有入栈),则进行搜索;若该点已经入栈,说明形成了环,则更新low

在DFS回溯时不断比较low,不断取最小的low值。如果dfn[x]==low[x] 则说明找到了一个强连通分量,并可以以x为起点。对栈进行弹出操作,直到x被弹出,这些被弹出的顶点构成一个强连通分量的顶点集

递归函数代码:

void tarjan(int now)
{
    dfn[now]=low[now]=++cnt;  //初始化
    stack[++t]=now;       //入栈操作
    v[now]=1;            //v[]代表该点是否已入栈
    for(int i=f[now];i!=-1;i=e[i].next)  //邻接表存图
        if(!dfn[e[i].v])           //判断该点是否被搜索过
        {
            tarjan(e[i].v);
            low[now]=min(low[now],low[e[i].v]); //回溯时更新low[ ],取最小值
        }
        else if(v[e[i].v])
            low[now]=min(low[now],dfn[e[i].v]); //一旦遇到已入栈的点,就将该点作为连通量的根
                         //这里用dfn[e[i].v]更新的原因是:这个点可能
                        //已经在另一个强连通分量中了但暂时尚未出栈,所
                        //以now不一定能到达low[e[i].v]但一定能到达
                        //dfn[e[i].v].
    if(dfn[now]==low[now])
    {
        int cur;
        do
        {
            cur=stack[t--];
            v[cur]=false;                       //不要忘记出栈
        }while(now!=cur);
    }
}

Kosaraju算法

Kosaraju给出的方案:对原图取反,然后从反向图的任意节点开始进行DFS的逆后序遍历。根据逆后序遍历得到的序列进行DFS遍历(与无向图找连通分量类似)一定可以找到各强连通分量。

DFS的逆后序遍历是指:如果当前顶点未访问,先遍历完与当前顶点相连的且未被访问的所有其它顶点,然后将当前顶点加入栈中,最后栈中从栈顶到栈底的顺序就是我们需要的顶点顺序。

也就是说,使用Kosaraju算法求强连通分量分为两步

  • 对原图取反,从任意一个顶点开始对反向图进行逆后续DFS遍历
  • 按照逆后续遍历中栈中的顶点出栈顺序,对原图进行DFS遍历,一次DFS遍历中访问的所有顶点都属于同一强连通分量


参考文章链接