有向图的强连通分量
强连通分量的定义
强连通分量是对于有向图来说的。通常,我们在对有向图做一些操作时,都是指在有向无环图(DAG, Directed Asyclic Graph) 来说的。所以将一个有向有环图转换为有向无环图是很有必要的。
对于一个有向图 $G(V, E)$ ,对于任意两个点 $x\in V, y\in V, x \neq y$ ,既存在 $x$ 到 $y$ 的路径,也存在 $y$ 到 $x$ 的路径,则称这个图是强连通图。
强连通分量 (SCC, Strongly Connected Component)是指一个有向图的极大强连通子图。
即:对于每个分量,在其中加入其他点,使得点数最多,且任意两个点都有一条互相可达的路径。
对于一个有向图,每个点都属于一个强连通分量,求出一个有向图的所有的强连通分量,将每个强连通分量看成一个点,这称为缩点。缩点后,就一定是一个有向无环图。
那么问题就转换为了如何求强连通分量。
求强连通分量的相关介绍
这里抛出几个概念:
dfn
dfn[i] 表示先按照 dfs 遍历点的先后顺序,第 i 个点是在第 dfn[i] 次访问一个新点时被访问到的,也叫作访问时间戳。
上面这个图,从 1 号点开始 dfs ,则 dfn 下图所示:即 dfs 遍历点的顺序为:1->2->4->3->5->6
以此,我们来介绍这个 dfs 搜索树上的四种边。
- 树枝边 (x, y),如图黑色边 ,在搜索树上,点 x 是点 y 的父亲节点,且
dfn[x] < dfn[y]
- 前向边 (x, y),如图黄色边 ,在搜索树上,点 x 是点 y 的祖先节点,且
dfn[x] < dfn[y]
- 后向边 (x, y),如图红色边 ,在搜索树上,点 y 是点 x 的祖先节点,且
dfn[x] > dfn[y]
- 横叉边 (x, y),如图绿色边 ,在搜索树上,两者不存在祖先和子辈的关系,即这两个点在 dfs 树上是他们 lca 下的不同子树的,且
dfn[x] > dfn[y]
low
low[u] 表示从 u 开始,遍历点 u 的子树,可以到达的最小的时间戳。
- 第一种情况:点 u 的儿子 v 还未被遍历,此时更新方式为
low[u] = min(low[u], low[v])
- 第二种情况:点 u 的儿子 v 已经在之前作为其他点的子树被遍历过了,但是还未结束遍历,此时更新方式为
low[u] = min(low[u], dfn[v])
,u->v
要么是后向边,u->v
要么是横叉边,故此时通过 dfn[v] 去更新 low[u],一定有更新后的 low[u] < dfn[u]。故:如果 v 在一个环中,u 也可以加入这个环,与他们一起构成一个连通分量。
事实上,这里取 low[u] = min(low[u], low[v]) 对于求强连通分量是无影响的,个人认为是正确的。
因为在 tarjan 这个算法的论文中的定义为:low 是最多通过一条反向边能到达的最小 dfn ,我们这里将 u->v
认为是一条反向边。但是实际上这个 low 最多通过一条反向边的限制并没有什么用,所以这里无所谓。
当然,在求解无向图的双连通分量时,这样更新 low 就会出现错误,所以这样的正确更新仅存在于有向图的强连通分量的求解。
为了不出错,也为了尊重作者,建议第二种情况还是使用论文中的更新方式,即 low[u] = min(low[u], dfn[v])
来更新。
写完无向图的双连通分量后,再来解释 这块实际会对求解产生错误的情况,实际是在针对求解 无向图的边双连通分量 的时候的,为了避免错误,建议还是统一一下。
stack
stack 表示栈中的元素,这个栈是存储从当前 u 开始的子树遍历到的所有点,当遍历结束后,如果发现 dfn[u] == low[u]
,则 u 是其所在连通分量 SCC 中 dfn 最小的点,那么就说明已经找完了 SCC 中所有的点,pop 出栈直到 pop 出 u 截止,即 SCC 中所有的点。
代码模板
|
|