Tarjan 求有向图的强连通分量

tarjan · 浏览次数 : 0

小编点评

Tarjan 算法是一种用于检测有向图中强连通分量的算法。强连通分量是指有向图中任意两点均可达的最大子图。Tarjan 算法通过深度优先搜索(DFS)来实现,并维护两个数组 dfn 和 low,分别表示每个节点的深度和最小深度。 1. 初始化:对每个节点,设置 dfn[u]=dfncnt=0,将节点 u 添加到栈 s 中,并设置 in_stack[u]=1。 2. 遍历节点的邻接表,对于每个未访问过的节点 v: - 如果 v 未被访问过,设置 low[v]=dfn[v]=++dfncnt,并将 v 添加到栈 s 中,设置 in_stack[v]=1。 - 如果 v 已经被访问过,但仍在栈 s 中,设置 low[u]=min(low[u], dfn[v])。 - 如果 v 已经被访问过,且不在栈 s 中,说明从祖先节点 x 到 v 形成了一个环,即存在一条返祖边。此时,将节点 u 添加到环中,并更新环的大小。 3. 回溯时,如果节点 u 满足 dfn[u]=low[u],说明 u 是它所属连通块的最小节点。将栈中所有节点弹出栈,并更新它们所属的强连通分量编号。 4. 最后,输出每个强连通分量的大小。 通过 Tarjan 算法,我们可以找到有向图中的所有强连通分量,并计算它们的大小。

正文

重温Tarjan, 网上看了许多博客感觉都讲的不清楚. 故传上来自己的笔记, 希望帮到大家.

提到的一些概念可以参考 oi wiki, 代码也是 oi wiki 的, 因为我不认为我能写出比大佬更好的代码了.


强连通分量: 有向图的最大强连通子图 ( 有向图中任意两点可达 )

  • Tarjan

    1. 对每个结点维护:

      • dfn[x]: 当前节点的 dfs 序.

      • low[x]: x 向下搜索能到达的最小 dfs 序.

    2. 更新 low:

      1. v 未被访问过: 初始 low[v] = dfn[v].v 入栈. 回溯时用 low[v] 更新它的 fa 的 low[ ].

      2. v 被访问过, 且还在栈中: 用 dfs[v] 更新 fa 的 low.

      3. v 被访问过, 不在栈中: 说明这是一个 fa 到 v 的单向访问, 跳过.

    3. 获取答案:

      能让 dfn[x] > low[x], 只有当 X 的子树中某个节点 C 有\(\begin {cases}1.一条横向边连接到一棵已遍历过的子树~A\\2.一条返祖边连接到~X~的祖先~xfa \end{cases}\) .

      1. 横向边: 说明 A 没有连接到 C 的边, 否则在之前 C 就被遍历了, 轮不到 X 来遍历. 就用是否 C 在栈中来排除这个情况, 子树 A 中的所有强连通分量之前已经出栈过了( 看代码的实现 ).
      2. 返祖边: 说明 xfa -> x -> c -> xfa 形成环, 在同一个强连通子图( 我们知道, 强连通图是许多环嵌套成的 ). 而且这个子图的根是 xfa 满足 dfn[xfa] = low[xfa].

      此时栈中进来过三类节点 :

      \[\begin {cases}1.~在~x~的子树中\begin {cases}1.~属于上述~xfa~循环的,~在同一个强连通子图.\\2.~不在同一个强连通子图,~那递归的讲,~在之前就因为属于某个~xfa'~(在~X~的子树中),而被踢出栈了.\end{cases}\\2. 不在~x~的子树中(即在已遍历过的子树中),~在栈中的位置一定在~x~的下面. \end{cases} \]

      故, 回溯时若节点符合 dfn[x] = low[x], 说明当前节点是它所属连通块的最小节点. 栈里它之上所有点都是一个强连通块.

代码:

 const int Maxn = 1e5 + 10;
    
    int dfn[Maxn], low[Maxn], dfncnt, s[Maxn], in_stack[Maxn], tp;
    int scc[Maxn], sc;  // 结点 i 所在 SCC 的编号
    int sz[Maxn];       // 强连通 i 的大小
    
    void tarjan(int u) {
        low[u] = dfn[u] = ++dfncnt, s[++tp] = u, in_stack[u] = 1;
        for (int i = head[u]; i; i = eg[i].nex) {
            const int &v = eg[i].to;
            if (!dfn[v]) {
                tarjan(v);
                low[u] = min(low[u], low[v]);
            } else if (in_stack[v]) {
                low[u] = min(low[u], dfn[v]);
            }
        }
        if (dfn[u] == low[u]) {
            ++sc;
            while (s[tp] != u) {
                scc[s[tp]] = sc;
                sz[sc]++;
                in_stack[s[tp]] = 0;
                --tp;
            }
            scc[s[tp]] = sc;
            sz[sc]++;
            in_stack[s[tp]] = 0;
            --tp;
        }
    }

与Tarjan 求有向图的强连通分量相似的内容: