Tarjan 算法找强连通分量

103 阅读2分钟

Tarjan 算法找强连通分量

什么是强连通分量?

连通: 对无向图而言,任意两点之间存在路径

强连通: 对有向图而言,任意两点之间存在路径

弱连通: 对有向图而言,将其视为无向图后任意两点之间存在路径

两种 dfs

第一种: 先访问当前节点,递再归出点

第二种: 先递归出点,再访问当前节点

⚠️注意:

每次到达时要做已到达的标记(而不是访问时标记),否则 dfs 会死循环. (这里的"访问"指输出之类的操作)

Tarjan 流程

时间戳: 每个节点带有时间戳标记(i,j),其中 i 表示 dfs 中到达该点的时间点,j 表示该点通过有向边可以回溯到的最早时间点.

  1. 到达一点时设置时间戳(到达时 i 和 j 相同),并将该点放入栈中
  2. 访问点时遍历其出点,更新 j
  3. 如果 j 不能更新(i==j),从栈顶开始出栈,构成一个强连通分量

🔮个人理解:

i 相当于节点的标号.

如果 j 不能更新则从栈顶开始出栈,表示这几个点可以相互到达,但不能到达更早的点.

⏱️时间复杂度:

由于完成 Tarjan 算法相当于执行依次完整的 dfs,时间复杂度为O(V2)O(V^2)(使用邻接矩阵)或O(V+E)O(V+E)(使用邻接表).

Code实现(c++)

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 10005;    // 最大点数
vector<int> graph[MAXN];   // 邻接表
int dfn[MAXN], low[MAXN];  // 访问时间点和回溯到的最早时间点
bool inStack[MAXN];        // 标记是否在栈中
int timestamp;             // 标记时间点
stack<int> stk;

int sccCount;              // 强连通分量个数
vector<vector<int>> sccs;  // 存储强连通分量

void tarjan(int x) {
    dfn[x] = low[x] = ++timestamp;  // 初始化时间戳
    stk.push(x);                    // 入栈
    inStack[x] = true;

    for (int v : graph[x]) {               // 遍历x的出点
        if (!dfn[v]) {                     // 如果没到达过v
            tarjan(v);                     // 从v开始dfs
            low[x] = min(low[x], low[v]);  // 要返回时更新x的low
        } else if (inStack[v]) {  // v已到达过但可能在同一分量中,只更新x的low
            low[x] = min(low[x], low[v]);
        }
    }

    if (dfn[x] == low[x]) {  // x的low无法更新时,确定强连通分量
        vector<int> scc;     // scc是当前的强连通分量
        int v = stk.top();
        while (v != x) {  // 从栈顶开始出栈,直到x
            stk.pop();
            inStack[v] = false;
            scc.push_back(v);
            v = stk.top();
        }
        stk.pop();  // 将x弹出
        inStack[x] = false;
        scc.push_back(x);

        sccs.push_back(scc);
        sccCount++;
    }
}

int main() {
    int V, E;  // 点数,边数
    cin >> V >> E;
    for (int i = 1; i <= E; i++) {  // 读入边
        int u, v;
        cin >> u >> v;
        graph[u].push_back(v);
    }

    for (int i = 1; i <= V; i++) {
        if (!dfn[i])  // dfn[i]==0表示i没到达过
            tarjan(i);
    }

    cout << "Total Sccs: " << sccCount << '\n';
    for (auto& scc : sccs) {
        for (int x : scc) {
            cout << x << ' ';
        }
        cout << '\n';
    }
    return 0;
}

PS:讲解思路来源于b站