设 z 是一个这样的点,从 z 出发存在一条路径到达 。若存在横叉边 ,则 、z 到 y 的路径、y 到 x 的路径构成一个环。
综上所述,栈中的节点就是能与从 x 出发的“后向边”和“横叉边”形成环的节点。进而可以引入“追溯值”的概念。 追溯值
设 表示流图的搜索树中以 x 为根的子树。x 的追溯值 定义为满足以下条件的节点的最小时间戳:
该点在栈中
存在一条从 出发的有向边,以该点为终点
根据定义,Tarjan算法按照以下步骤计算“追溯值”:
当节点 x 第一次被访问时,把 x 入栈,初始化
扫描从 x 出发的每一条边
若 y 没有被访问过,则说明 是“树枝边”,递归访问 y ,从 y 回溯后,令
若 y 被访问过且 y 在栈中,则令
从 x 回溯之前,判断是否有 。若成立,则不断从栈中弹出节点,直至 x 出栈
下页图中的中括号【】里的数值标注了每个节点的的“追溯值”
强连通分量判定法则
在追溯值的计算过程中,若从 x 回溯前,有 成立,则栈中从 x 到 栈顶的所有节点构成一个强连通分量。
大致来说,在计算追溯值的第三步,如果 ,那么说明 中的节点不能与栈中其他结点一起构成环。另外,因为横叉边的终点时间必然小于起点时间戳,所以中的结点也不可能直接到达尚未访问的结点(时间戳更大)。综上所述,栈中从 x 到栈顶的所有节点不能与其他结点构成环。
由因为我们及时进行了判定和出栈操作,所以从 x 到栈顶的所有节点独立构成一个强连通分量。
Tarjan算法模板
void tarjan(int u)
{
dfn[u] = low[u] = timestamp;
stk[++ top] = u, in_stk[u] = true;
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(!dfn[j])
{
tarjan(j);
low[u] = min(low[u], low[j]);
}
else if(in_stk[j])
low[u] = min(low[u], dfn[j])
}
if(dfn[u] == low[u])
{
int y;
++ scc_cnt;
do{
y = stk[top ++ ];
in_stk[y] = false;
id[y] = scc_cnt;
}while(y != u)
}
}
复制代码
缩点
我们可以把每一个 SCC 缩成一个点。对于原图中的每条有向边 若 ,则在编号为 与编号为 的SCC之间连边。
最终,我们会得到一个有向无环图(DAG)
[code]for(int x = 1; x