Connect
强连通分量¶
强连通分量即一个点集,集合中任意一对结点(u, v) 都互相可达
Tarjan算法¶
Tarjan算法输入为一张有向图,执行一次dfs,输出为多个点的集合,每个集合是一个强连通分量
具体方法为进行多次dfs,对每个节点记录两个值
dfn
: 这个节点的编号,从1开始,访问到一个空白点即标记,并将序号加1low
: 这个点能访问到的节点的最小dfn
值,一开始置为自身的dfn
tarjan(u) {
DFN[u] = Low[u] = ++Index // 为节点u设定次序编号和Low初值
Stack.push(u) // 将节点u压入栈中
for each (u, v) in E // 枚举每一条边
if (v is not visted) // 如果节点v未被访问过
tarjan(v) // 继续向下找
Low[u] = min(Low[u], Low[v])
else if (v in S) // 如果节点v还在栈内
Low[u] = min(Low[u], DFN[v])
if (DFN[u] == Low[u]) // 如果节点u是强连通分量的根
repeat
v = S.pop // 将v退栈,为该强连通分量中一个顶点
print v
until (u== v)
}
算法思想其实就在于计算每个点可以向前访问搜索子树中点,然后一直向前追溯,直到遇到一个点无法访问更前面的节点,那么从栈顶到这个节点的这些节点都可以互相访问,是一个强连通分量,没有仔细去思考证明,但大概思路可以这样想
设u的dfn
和low
相等,此时u上面还在栈内的有若干元素,那么从u开始,都可以访问到这些节点,因为是从u开始执行的dfs,而反过来,因为后面这些点都有指向前面的边,可以这些节点都可以访问到u,因此是强连通分量
割点和割边¶
割点和割边是对于无向图来说的
- 割点: 去掉这个点后,整张图连通分量个数增加
- 割边: 去掉这个边后,整张图连通分量个数增加
可以用Tarjan算法来求,和求强连通分量的Tarjan有所区别,不过整体流程类似
对每个节点,求两个值
- dfn: dfs搜索到一个节点时的序号
- low: 节点除过父节点外可以访问到的最小序号
算法思路可以这样想,对一个连通分量,一次dfs一定可以完全搜索完,在搜索过程中,逐个来看每个节点是不是割点,每条边是不是割边即可
这里判断删除一个点、一条边后连通分量是否增加,就看搜索过的部分和之后的部分会不会分开,即看孩子节点能否绕过父节点到达祖先节点
判断方法是
- 割点,这里要根绝是否是根节点进行区分
- 根节点: 看彼此无法连接的子节点是否多于一个,这里在外围进行计算,对根节点每个孩子开始遍历,如果孩子的low还没有设置,运行tarjan算法,并且根节点子树数量+1,最后看子树数量是否大于1
- 其它节点: low(v) <= dfn(u)
- 割边: low(v) < dfn(u)
这里low(v)=dfn(u)
指存在以下情况,v1可以通过v2连接到u,那么删除u,同样可以分割,所以割点判断条件中加入相等;而对于割边,则不成立,因此割边必须是严格偏序关系
u
/ \
v1 -- v2