Kosaraju 求强连通块

// g 是原图,g2 是反图

void dfs1(int u) {
  vis[u] = true;
  for (int v : g[u])
    if (!vis[v]) dfs1(v);
  s.push_back(u);
}

void dfs2(int u) {
  color[u] = sccCnt;
  for (int v : g2[u])
    if (!color[v]) dfs2(v);
}

void kosaraju() {
  sccCnt = 0;
  for (int i = 1; i <= n; ++i)
    if (!vis[i]) dfs1(i);
  for (int i = n; i >= 1; --i)
    if (!color[s[i]]) {
      ++sccCnt;
      dfs2(s[i]);
    }
}

这是 Kosaraju,码搬自 oiwiki。该算法依靠两次简单的 DFS 实现:

第一次 DFS,选取任意顶点作为起点,遍历所有未访问过的顶点,并在回溯之前给顶点编号,也就是后序遍历.

第二次 DFS,对于反向后的图,以标号最大的顶点作为起点开始 DFS.这样遍历到的顶点集合就是一个强连通分量.对于所有未访问过的结点,选取标号最大的,重复上述过程.

正确性证明:第一次 DFS,设有编号小的点 \(x\),标号大的点 \(y\)。有两种情况:要么 \(y\)\(x\) 的祖先点,即 \(y\) 可以走到 \(x\);要么 \(y\) 走不到 \(x\),但 \(x\) 先遍历。\(y > x\) 所以 \(y\) 不可能走到 \(x\)

第二次 DFS,这次 DFS 从大搜到小,设从 \(y\) 开始搜,搜到了 \(x\),则由于是反图有 \(x \to y\),如果这对 \((x,y)\) 在第一次 DFS 属于第一种情况,则有 \(y \to x\),这就符合强连通分量了;如果属于第二种情况(假设),那么 \(x\)\(y\) 先遍历,因此在遍历的时候一定会搜到 \(x\)\(y\) 的路径,结果标号会 \(x > y\),与定义矛盾。因此这样搜出来的一定是强连通分量。

感觉是一种很神奇的方法。

posted @ 2026-07-09 23:04  Jenny_yu  阅读(5)  评论(0)    收藏  举报