美文网首页
图算法(二)Tarjan

图算法(二)Tarjan

作者: maxkibble | 来源:发表于2018-07-09 21:16 被阅读0次

    在一次BFS或DFS中,我们其实并不能保证一定访问到图中的所有节点,因为有些图可能是不连通的。我们把从一个点出发,所有可达点的集合称为这个点所在的连通分量。给定一个无向图,我们找所有连通分量的方法叫做灌水法(Flood Fill),其实就是对当前未访问过的点做BFS/DFS,直到所有的点都被访问过1次。
    Tarjan算法是为了解决有向图中类似的问题提出的。只不过有向图中我们可以定义强连通分量,有向图中一个强连通分量中的任意两个点u,v都是强连通的,即存在从u到v的路径,也存在从v到u的路径。

    strong-connected.png

    很明显,Flood Fill并不能用来求强连通分量。但只使用BFS/DFS,我们可以给出一个求给定点所在强连通分量的方法:1) 从该点出发做一次BFS/DFS;2) 把所有边反向,再从这个点做一次BFS/DFS;3) 把两次搜索访问的顶点集合做一次交,就可以得到该点所在的极大强连通分量。如果用这种方法求所有强连通分量的话,需要对每个点做两次BFS/DFS。更好的方法是Kosaraju算法或Tarjan算法,这里只介绍Tarjan,它只需要对图做一遍DFS。

    在进入Tarjan算法之前,你需要明白对图做DFS可以得到一棵树,树的根节点是DFS的起点,每次从点u访问一个的点v,就引一条从u到v的边。

    Tarjan算法中,图中每个节点维护两个属性:

    1. dfu(i):节点i在DFS中第dfu(i)个被访问到(时间戳);
    2. low(i):DFS搜索树中,以节点i为根节点的子树中的节点集合记为T(i)。T(i)中的点在原图中所指向的点的集合记为S(i)。S(i)中最小的时间戳就是low(i)。

    Tarjan算法的描述如下:

    1. 初始化一个空的栈和一个每访问一个节点加1的计时器
    2. 对图做DFS,每次访问新的顶点时,设定dfn(i)为当前时间,把该节点压栈,接着求low(i):对于还没被访问的后继节点j,递归访问j,low(i) = min(low(i), low(j)),对于已经访问过的且在栈中的后继节点j,low(i) = min(low(i), dfn(j))。如果最终得到的low(i) = dfn(i),就把栈中当前节点以上的节点全部弹出,这些节点就是一个极大强连通分量

    Tarjan算法只需要做一遍DFS,所以一定会终止,时间复杂度O(m+n)

    C++实现:

    void tarjan(int u) {
        dfn[u] = low[u] = ++cnt;
        st[++top] = u;
        instack[u] = true;
    
        for(Edge *p = e[u]; p; p = p->next) {
            int v = p->dst;
            if(!dfn[v]) {
                tarjan(v);
                low[u] = std::min(low[u], low[v]);
            }
            else if(instack[v])
                low[u] = std::min(low[u], dfn[v]);
        }
        
        if(dfn[u] == low[u]) {
            cluster++;
            int hd;
            do {
                hd = st[top--];
                instack[hd] = false;
                belong[hd] = cluster;
            }
            while(hd != u);
        }
    }
    

    (PS:一道经典的求强连通分量的题 Networks of School

    相关文章

      网友评论

          本文标题:图算法(二)Tarjan

          本文链接:https://www.haomeiwen.com/subject/azztpftx.html