Commit 2920b972 authored by sshwy's avatar sshwy
Browse files

resolve conversation

parent 34760180
Loading
Loading
Loading
Loading
+14 −14
Original line number Diff line number Diff line
@@ -37,22 +37,22 @@ Tarjan 发明了很多算法结构。光 Tarjan 算法就有很多,比如求

我们考虑 DFS 生成树与强连通分量之间的关系。

如果结点 u 是某个强连通分量在搜索树中遇到的第一个结点,那么这个强连通分量的其余结点肯定是在搜索树中以 u 为根的子树中。u 被称为这个强连通分量的根。
如果结点 $u$ 是某个强连通分量在搜索树中遇到的第一个结点,那么这个强连通分量的其余结点肯定是在搜索树中以 $u$ 为根的子树中。$u$ 被称为这个强连通分量的根。

反证法:假设有个结点 v 在该强连通分量中但是不在以 u 为根的子树中,那么 u 到 v 的路径中肯定有一条离开子树的边。但是这样的边只可能是横叉边或者反祖边,然而这两条边都要求指向的结点已经被访问过了,这就和 u 是第一个访问的结点矛盾了。得证。
反证法:假设有个结点 $v$ 在该强连通分量中但是不在以 $u$ 为根的子树中,那么 $u$ 到 $v$ 的路径中肯定有一条离开子树的边。但是这样的边只可能是横叉边或者反祖边,然而这两条边都要求指向的结点已经被访问过了,这就和 $u$ 是第一个访问的结点矛盾了。得证。

### Tarjan 算法求强连通分量

在 Tarjan 算法中为每个结点 u 维护了以下几个变量:
1\. $DFN[u]$ :深度优先搜索遍历时结点 u 被搜索的次序。
2\. $low[u]$ :设以 u 为根的子树为 $Subtree(u)$ 。 $low[u]$ 定义为以下结点的 $DFN$ 的最小值: $Subtree(u)$ 中的结点;从 $Subtree(u)$ 通过一条不在搜索树上的边能到达的结点。
在 Tarjan 算法中为每个结点 $u$ 维护了以下几个变量:
1\. $DFN[u]$ :深度优先搜索遍历时结点 $u$ 被搜索的次序。
2\. $LOW[u]$ :设以 $u$ 为根的子树为 $Subtree(u)$ 。 $LOW[u]$ 定义为以下结点的 $DFN$ 的最小值: $Subtree(u)$ 中的结点;从 $Subtree(u)$ 通过一条不在搜索树上的边能到达的结点。

显然,按照 DFS 搜索树的递归顺序, $low[u]$ 是单调递增的。
显然,按照 DFS 搜索树的递归顺序, $LOW[u]$ 是单调递增的。

按照深度优先搜索算法搜索的次序对图中所有的结点进行搜索。在搜索过程中,对于结点 $u$ 和与其相邻的结点 $v$ (v不是u的父节点)考虑 3 种情况:

1.   $v$ 未被访问:继续对 $v$ 进行深度搜索。在回溯过程中,用 $low[v]$ 更新 $low[u]$ 。因为存在从 $u$ 到 $v$ 的直接路径,所以 $v$ 能够回溯到的已经在栈中的结点, $u$ 也一定能够回溯到。
2.   $v$ 被访问过,已经在栈中:即已经被访问过,根据 $low$ 值的定义(能够回溯到的最早的已经在栈中的结点),则用 $DFN[v]$ 更新 $low[u]$ .
1.   $v$ 未被访问:继续对 $v$ 进行深度搜索。在回溯过程中,用 $LOW[v]$ 更新 $LOW[u]$ 。因为存在从 $u$ 到 $v$ 的直接路径,所以 $v$ 能够回溯到的已经在栈中的结点, $u$ 也一定能够回溯到。
2.   $v$ 被访问过,已经在栈中:即已经被访问过,根据 $LOW$ 值的定义(能够回溯到的最早的已经在栈中的结点),则用 $DFN[v]$ 更新 $LOW[u]$ .
3.   $v$ 被访问过,已不在在栈中:说明 $v$ 已搜索完毕,其所在连通分量已被处理,所以不用对其做操作。

将上述算法写成伪代码:
@@ -68,9 +68,9 @@ Tarjan 发明了很多算法结构。光 Tarjan 算法就有很多,比如求
            else if v has been in the stack then
                low[u]=min(low[u],dfn[v])

对于一个连通分量图,我们很容易想到,在该连通图中有且仅有一个 $DFN[u]=low[u]$ 。该结点一定是在深度遍历的过程中,该连通分量中第一个被访问过的结点,因为它的 DFN 值和 $low$ 值最小,不会被该连通分量中的其他结点所影响。
对于一个连通分量图,我们很容易想到,在该连通图中有且仅有一个 $DFN[u]=LOW[u]$ 。该结点一定是在深度遍历的过程中,该连通分量中第一个被访问过的结点,因为它的 DFN 值和 LOW 值最小,不会被该连通分量中的其他结点所影响。

因此,在回溯的过程中,判定 $DFN[u]=low[u]$ 的条件是否成立,如果成立,则栈中从 $u$ 后面的结点构成一个 SCC。
因此,在回溯的过程中,判定 $DFN[u]=LOW[u]$ 的条件是否成立,如果成立,则栈中从 $u$ 后面的结点构成一个 SCC。

### 实现

@@ -99,13 +99,13 @@ void tarjan(int u) {

## Kosaraju 算法

Kosaraju 算法依靠两次简单的 dfs 实现。
Kosaraju 算法依靠两次简单的 DFS 实现。

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

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

两次 dfs 结束后,强连通分量就找出来了,Kosaraju 算法的时间复杂度为 $O(n+m)$
两次 DFS 结束后,强连通分量就找出来了,Kosaraju 算法的时间复杂度为 $O(n+m)$

### 实现