Tarjan算法
\(Tarjan\)算法
\(Tarjan\)求强连通分量
概念:
如果两个顶点互相可达,则它们是强连通的。如果一幅有向图中任意两个顶点都是强连通的,则这幅有向图也是强连通的。 强连通分量就是图中具有连通性的一个最大子集,一般可以用来缩点,即相互到达的一堆点可以将他们有用的信息统一到一个点上去。求解强连通分量的方法一般会使用\(Tarjan\)算法。
首先我们需要学会\(dfs\)树,定义几种边:
树边,连接\(dfs\)树中的父节点和子节点的边。
横叉边,连接\(dfs\)树中的不在一个子树中的两个节点的边。
前向边,连接\(dfs\)树中的一个节点连向他的子树中深度差大于等于2,也就是不直接相连的两点的边。
后向边,连接\(dfs\)树中的一个节点连向他的祖先的边,又叫返祖边。
求解:
\(dfn[i]\)表示i节点的时间戳,\(low[i]\)表示i节点的所属的强连通分量i的时间戳的最小值,也可以说是i或i的子树能够追溯到的最早的栈中节点的时间戳 。
发现如果在\(dfs\)树中出现了后向边说明后向边连接的两点之间的环一定是强连通的,但不一定是最大子集。
我们有个性质:如果\(low[i]\)比\(dfn[i]\)小,说明他有一条连向祖宗的后向边,所以要保留在栈中。如果当前\(low[i]\)等于\(dfn[i]\)说明他搜索的栈中没有可以连向他祖宗的边,当前栈中的点一定就是\(low[栈中的点]\)等于\(dfn[i]\)的点,也可以有能连向i的反向边。
有如果树边没有被找到过,则有
\(low[u]=min(low[u],low[v]);\)也就是用v的所能连向的点的最小时间戳来更新u。
如果此点已经入栈,说明此时这是一条回边,因此不能用\(low[v]\)更新,而应该:
\(low[u]=min(low[u],dfn[v]);\)因为,\(low[v]\)可能属于另一个编号更小的强连通分量里,而u可以连接到v但v不一定与u相连,所以可能u、v并不属于同一个强连通分量。但是第一种情况如果\(low[v]\)比\(low[u]\)小的话,v一定有包括u的强连通分量,
\(Tarjan\)求割点和桥
概念:
在无向连通图中,双连通分量分为点双和边双,点双为一个连通分量删去任意一个点都保持连通,边双为删去任意一条边都保持连通。连接点双的点为割点,连接边双的点为桥,割点和桥的性质就是删去他们就会使原图不连通,因为如果连通说明被连接的点双或边双仍可以组成一个更大的点双或边双。
求解:
我们仍可用求强连通分量的做法,用dfn和low数组。此时的low数组为能追溯到的最早的栈中的节点的时间戳,我们可以用求强连通分量的方法更新。我们每次枚举的时候,搜索还没有被搜索到的点,也就是原图中多个连通块的任意一点,然后向下搜索。对于树边,继续搜索,回边我们也用dfn更新,因为无向图是双向边,树边同时对应着一个后向边,因此每个点都会有个访问父亲的机会,我们用dfn更新可以防止回到自己回到祖宗而不是儿子回到祖宗。
如果u不为根节点,即对于u连向的且没有找到过的点,先求出该点的low值,该点的low值==dfn[u],说明该点的最早能追溯到的最早的栈中节点就是u,因此删去u之后,v无法相连到u的祖宗,因此他为割点。
如果u为根节点,只需要判断他有几个连边就行。