【定义】
对于一个有向图$G$,其连通分量为:对于分量中任意两点$u,v$,必然可以从$u$走到$v$,且从$v$走到$u$。
强连通分量就是极大连通分量。即一个连通分量加上任何一些点之后它都不是一个连通分量,那么就把它称为强连通分量。
求强连通分量的意义在于能够把任意一个有向图经过缩点之后转化成一个有向无环图(DAG图),缩点是指将所有强连通分量缩成一个点。
【Tarjan算法】
首先我们按DFS序遍历一个图,那么这个图中的所有边可以分为四种:
那么某个点如果在强连通分量中,有以下两种情况:
- 存在后向边指向其祖先节点;
- 经过某个横叉边先走到了点$y$,然后点$y$存在前向边走到了某个祖先节点。
在Tarjan算法中我们需要引入一个时间戳的概念,我们按照DFS遍历的顺序给每个结点一个编号,假设为$1\sim N$,那么对于后向边$(x,y)$,$y$的时间戳一定小于$x$,横叉边也是如此。
对于每个点,我们定义两个时间戳:
- $dfn[u]$:表示第一次遍历到$u$时的时间戳;
- $low[u]$:表示从点$u$开始走所能遍历到的最小的时间戳。
那么如果有$dfn[u]==low[u]$,说明点$u$是其所在的强连通分量的最高点。那么我们就可以把$u$所在的这个强连通分量找出来。
【Tarjan算法代码框架】
void tarjan(int u)
{
dfn[u] = low[u] = ++timestamp;//刚遍历到的时候先初始化当前点的时间戳
stk.push(u), in_stk[u] = true;//将当前点加到栈中,然后标记这个点在栈中
for (int i = h[u]; ~i; i = ne[i])//然后遍历u能到的所有邻点
{
int j = e[i];
if (!dfn[j])//如果当前这个点还没被遍历过
{
tarjan(j);//遍历一下
low[u] = min(low[u], low[j]);//儿子能到的最小时间戳u也能到
}
else if (in_stk[j])//如果当前这个点还是在栈中
low[u] = min(low[u], dfn[j]);//那么就用当前点的时间戳更新一下u的low值
}
if (dfn[u] == low[u])//如果u为某个强连通分量的最高点
{
int t;
scc_cnt++;//强连通分量的数量
do
{
t = stk.top();
stk.pop();
in_stk[t] = false;
id[t] = scc_cnt;//标记这个点是属于哪个强连通分量
} while (t != u);
}
}