简介 Tarjan算法由罗伯特·塔杨发明,是一个基于DFS的线性时间复杂度算法,主要用于求解有向图的强连通分量和无向图的割点与桥。
在这里我们继续统一使用祖传邻接表(链式前向星)存储图,邻接表的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 constexpr int maxn = 100000 + 5 ;int head[maxn], cnt = 0 ;struct Edge { int v, next; } edges[maxn * 2 ]; void add_edge (int u, int v) { edges[cnt].v = v; edges[cnt].next = head[u]; head[u] = cnt++; }
基本原理 从任意一个节点出发,构造它的一棵DFS树,并且记录下访问的时间戳dfn
数组,同时用low
数组表示能够构成闭合回路的DFS树的结点中时间戳的最小值,最后通过low
的值和dfn
的值判断所需要求解的答案。
应用一:有向图的强连通分量 有向图强连通分量:在有向图$ G $中,如果两个顶点$ v_i $, $ v_j $间($ v_i $ > $ v_j $)有一条从$ v_i $到$ v_j $的有向路径,同时还有一条从$ v_j $到$ v_i $的有向路径,则称两个顶点强连通。如果有向图$ G $的每两个顶点都强连通,称$ G $是一个强连通图。有向图的极大强连通子图,称为强连通分量。 强连通分量是Tarjan算法最经典的应用,详细解释写在注释中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void tarjan (int u) { dfn[u] = low[u] = time++; stack [++top] = u; inStack[u] = true ; for (int e = head[u]; e; e = edges[e].next) { int v = edges[e].v; if (!dfn[v]) { tarjan(v); low[u] = min (low[u], low[v]); } else if (inStack[v]) low[u] = min (low[u], dfn[v]); } if (dfn[u] == low[u]) { int cur; do { cur = stack [top--]; inStack[cur] = false ; } while (cur != u); } }
应用二:无向图的割点与桥 割点:若从图中删除节点 x 以及所有与 x 关联的边之后,图将被分成两个或两个以上的不相连的子图,那么称 x 为图的割点。 桥:若从图中删除边 e 之后,图将分裂成两个不相连的子图,那么称 e 为图的桥或割边。
在无向图加边时,我们一次加两条相互反向的边,假设其中一条编号为$ e $, 则可以证明其反向边编号就为$ e ^ 1 $。(编号从0开始)
代码一:桥 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 bool bridge[maxn * 2 ];void tarjan (int u, int eu) { dfn[u] = low[u] = time++; for (int e = head[u]; e; e = edges[e].next) { int v = edges[e].v; if (!dfn[v]) { tarjan(v, e); low[u] = min (low[u], low[v]); if (low[v] > dfn[u]) bridge[e] = bridge[e ^ 1 ] = true ; } else if (e != eu ^ 1 ) low[u] = min (low[u], dfn[v]); } }
代码二:割点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 bool cut[maxn];void tarjan (int u, int eu, int root) { dfn[u] = low[u] = time++; int children = 0 ; for (int e = head[u]; e; e = edges[e].next) { int v = edges[e].v; if (!dfn[v]) { children++; tarjan(v, e); low[u] = min (low[u], low[v]); if (u != root && low[v] >= dfn[u]) cut[u] = true ; } else if (e != eu ^ 1 ) low[u] = min (low[u], dfn[v]); } if (children >= 2 && u == root) cut[u] = true ; }
总结: 搞不懂就背下来
喜欢我的不妨继续关注哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦!ヾ(≧▽≦*)o