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\),与定义矛盾。因此这样搜出来的一定是强连通分量。
感觉是一种很神奇的方法。

浙公网安备 33010602011771号