【算法模板】tarjan

Tarjan算法基于深度优先搜索, 每个强连通分量为搜索树中的一棵子树. 搜索时, 把当前搜索树中未处理的结点加入堆栈, 回溯时就可判断栈顶到栈中的结点是否为一个强连通分量.
dfn[u]: 记录结点u在DFS过程中被遍历到的次序号(时间戳).
对于一棵DFS树, 对于原图中的非树边, 为便于描述, 定义:
前向边: 祖先->儿子的边.
后向边: 儿子->祖先的边.
横叉边: 没有祖先儿子关系的边(注意横叉边
只会往dfn减小的方向连接).
low[u]: 记录结点u或u的子树能够追溯到的dfn最小的值(栈中标号的最小点)

由定义, 显然有:
⚫ 若(u,v)为树边, low[u] = min{dfn[u], low[v]};
⚫ 若(u,v)是指向栈中结点的后向边, low[u] = dfn(v).
定理: 当dfn[u] = low[u]时, 以u为根的搜索子树上所有结点构成一个强连通分量.
证明: dfn[u]表示u点被DFS到的时间, low[u]表示u和u所有的子树所能到达的点中dfn最小值.
dfn[u]=low[u], 这说明u点及u的子树结点最多只有指向u点的边, 而没有指向u的祖先的边了.
显然, 遍历过的结点从u出发又最终回到u形成一个环, 即u点与它的子孙结点构成了强连通分量.

inline void paint(int x) {
    s.pop(); ins[x] = 0;
    color[x] = colorNum;
    colorSize[colorNum]++;
}

void tarjan(int u) {
    dfn[u] = low[u] = ++dfnn;
    s.push(u);
    ins[u] = vis[u] = 1;

    for (int i = 0; i < g[u].size(); i++) {
        int v = g[u][i];
        if (!dfn[v]) {  //前向边
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if (ins[v])    //后向边(排除横叉边)
            low[u] = min(low[u], dfn[v]);
    }
    if (dfn[u] == low[u]) {
        colorNum++;
        while(s.top() != u) {
            int x = s.top();
            paint(x);
        }
        paint(u);
    }
}
posted @ 2022-04-06 16:30  _vv123  阅读(39)  评论(0编辑  收藏  举报