Tarjan 系列算法学习总结 - 有向图强连通分量,无向图割顶和桥,无向图双连通分量
Tarjan算法求有向图强连通分量
常用场景:
- 求顶点基:求出强连通分量后缩点,得到DAG。在入度为0的点中,每个强连通分量中任取一个点,可构成顶点基。
核心思想:
- 注意每个节点在一个且仅在一个强连通分量中。
- 当dfs第一次访问到某一个强连通分量时,将这个被初次访问的节点记为根节点,其所在的强连通分量必定被包含在以此节点为根的DFS子树之中。
- 一个点为根节点,当且仅当其本身和其子节点无法访问到比该节点更早的节点。
- $dfn[i]$保存节点dfs序,$low[i]$保存节点所能访问到的dfs序最靠前的点,根据定义来更新。
View Code1 // 求强连通分量,通常可以对有向图中强连通分量进行缩点。生成DAG图 2 #include <stack> 3 #include <vector> 4 using namespace std; 5 6 const int maxn = 100000; 7 8 //接口: 邻接表 多组数据时 需要所有过程量初始化为0; 9 vector <int> G[maxn]; 10 11 //说明: Tar 为记录未处理的回溯栈 12 //dfn[i] 第i个节点的dfs序 13 //low[i] 第i个节点所能访问到的最早祖先(dfs序值) 14 //注意 由于随意取点 因此dfs要将所有的点遍历 节点编号是从 1开始 15 stack <int> Tar; 16 int dfn[maxn], low[maxn]; 17 bool instack[maxn]; 18 int index = 0; 19 int Bcnt = 0; 20 //结果 Bcnt 表示强连通分量的数目 21 int Tarjan(int i) 22 { 23 dfn[i] = low[i] = ++index; 24 instack[i] = true; 25 Tar.push(i); 26 for (int j = 0; j < G[i].size(); j++) 27 { 28 if (!dfn[G[i][j]]) 29 { 30 Tarjan(G[i][j]); 31 if (low[i] > low[G[i][j]]) low[i] = low[G[i][j]]; 32 } 33 else if (instack[G[i][j]] && low[i] > dfn[G[i][j]]) low[i] = dfn[G[i][j]]; 34 } 35 if (dfn[i] == low[i]) { 36 Bcnt++; 37 38 while(!Tar.empty()) 39 { 40 int j = Tar.top(); 41 Tar.pop(); 42 instack[j] = false; 43 /* j是当前第Bcnt个强连通分量中的节点 44 按需要操作 45 */ 46 if (j == i) break; 47 48 } 49 } 50 }
Tarjan算法求无向图割点和桥
与求强连通分量的思想类似:
- 每次开始dfs最先访问的点即dfs树的树根,该点是割点,当且仅当存在两个或者更多子节点时,其是割点。
- 非根节点$u$是割点,当且仅当其存在一个子节点$v$,使得$v$及其子节点都无法通过除了边$(u,v)$ 外访问回其根节点的祖先,即 $ dfn[u] \leq low[v] $。
- $(u,v)$ 是桥当且仅当$v$及其子节点都无法通过除了边$(u,v)$ 外访问回其根节点及其祖先,即 $ dfn[u] < low[v] $。注意割点的定义,桥并不要求dfs树树根特判。
- 实现细节上,由于无向图边的双向性,dfs时需要同时考虑其父亲节点不能重复访问。
- 代码板子待补
Tarjan算法求无向图双连通分量
无向图中双连通分量分为点双连通和边双连通。
边双连通图:将所有的桥删去,剩下各联通块的部分就是双连通分量;
点双连通图:
- 有几个需要注意的点,首先栈内保存的应当是边,而不是点,因为双连通分量之间可能共享某一个割点。
- 从刘汝佳训练指南的板子中我们需要一点更正,这个模板求出的双连通分量的定义应该是“子图内部不含割点的连通分量”,当该连通分量所含点数大于2时,才存在环。


浙公网安备 33010602011771号