Tarjan 算法学习笔记
无向图上的 Tarjan
定义:
- \(dfn_u\):点 \(u\) 的时间戳,记录点 \(u\) 是 DFS 过程中被访问的时间。
- \(low_u\):记录点 \(u\) 经过至多一条非树边,所能达到的 dfn 最小的节点。
对于 DFS 中每一条遍历到的边 \((u,v)\):
- 若搜索树上 \(v\) 是 \(u\) 的儿子,即边 \((u,v)\) 是树边,则有 \(low_u\gets \min(low_u, low_v)\)。
- 否则边 \((u,v)\) 一定是返祖边,即 \(v\) 是 \(u\) 的祖先,则有 \(low_u=\min(low_u,dfn_v)\)。
- 可以发现对无向图进行 DFS 遍历时,是不可能存在横叉边的。
割点
一个点是割点的充要条件是:
- 若点 \(u\) 为搜索树的根,且 \(u\) 有两个以上的儿子,则 \(u\) 是割点。
- 否则,如果存在 \(u\) 的一个儿子 \(v\),满足 \(low_v\ge dfn_u\),则 \(u\) 是割点。
点双连通分量
- DFS 过程中,用栈来存储被访问过的点。
- 对于点 \(u\),如果它的儿子 \(v\) 满足 \(low_v\ge dfn_u\),则 \(u\) 是割点。不断弹栈,直到弹出 \(v\);此时弹出的点和 \(u\) 就构成了一个点双。
- DFS 结束后,栈中剩下的点们构成一个点双。
- 割点会存在于多个点双之中,其他点以及每一条边只会存在一个点双里。
割边
- 首先非树边不可能成为割边。
- 对于树边 \((u,v)\),其中 \(u\) 是 \(v\) 的父亲;如果 \(low_v > dfn_u\),则边 \((u,v)\) 是割边。
- 或者如果 \(low_u = dfn_u\),则 \(u\) 和 \(u\) 的父亲之间的树边是割边。
- 原理就是如果 \(v\) 无法通过返祖边和 \(u\) 的祖先联通,则 \((u,v)\) 是割边。
边双连通分量
- 同样建立栈,将遍历到的点依次压入栈中。
- 如果 \(low_u = dfn_u\),则 \(u\) 和 \(u\) 的父亲是割边;则不断弹栈,直到弹出点 \(u\)。然后将这些刚弹出的点归入一个边双中。
- 割边不会存在于任何一个边双里;其余边和所有点会恰好存在于一个边双里。
- 容易发现,缩完点后的图会变成一棵优美的树。
有向图上的 Tarjan
定义:
- \(dfn_u\):点 \(u\) 的时间戳,记录点 \(u\) 是 DFS 过程中被访问的时间。
- \(low_u\):记录点 \(u\) 经过至多一条返祖边,所能达到的 dfn 最小的节点。
强连通分量
定义:
- 如果两个点 u,v,存在 u 到 v 的路径,且存在 v 到 u 的路径,则称这两个点强连通。
- 所有点都强连通的图称为强连通图。
- 我们把图中极大的强连通子图称为强连通分量。
求法:
- 同样开一个栈,将 dfs 到的点依次压入栈里面。
- 每次更新 \(low_u\) 时,如果是横叉边且那个点无法回到 \(u\) 的某个祖先(即那个点不在栈里面)则无视。
- 当 \(dfn_u=low_u\) 时,则不断弹栈,直到弹出 \(u\),然后将这些点归入一个 SCC 中。
- 缩完点后的图是个 DAG。