Tarjan 算法——图论学习笔记
Part.1 引入
在图论问题中,我们经常去研究一些连通性问题,比如:
- 有向图的联通性:传递闭包——Floyd 算法;
- 有向图连通性的对称性:强联通分量(SCC)——Tarjan 算法缩点;
- 无向图的联通性:并查集;
- 无向图的关键边:桥(割边)、边双——Tarjan 算法缩点;
- 无向图的关键点:割点、点双——Tarjan 建立圆方树。
那么,Tarjan 算法到底是什么呢?
Part.2 Tarjan 算法求 SCC
SCC,即强联通分量,是一张有向图的极大子图,满足任意两个点 \(u,v\) 强联通(即 \(u\) 可以到 \(v\),\(v\) 可以到 \(u\))。一个重要的性质就是强联通具有传递性。
在有向图中,我们会用 Tarjan 算法去建一棵 DFS 生成树:

在 DFS 的过程中,我们会对于每个点,处理出一个 DFS 序号,并把 DFS 到的点放入一个栈中,定义一个新数组 \(low\),其中,\(low_x\) 表示 \(x\) 号节点能跳到的在栈中的 DFS 序的最小值。
接下来就是 Tarjan 算法的核心思想:对于一个点 \(u\),如果满足 \(dfn_u=low_u\),说明点 \(u\) 不存在向上跳的非树边,是一个顶点。那么当前节点及以后在栈中的节点就构成了一个 SCC。
代码如下:
int dfn[N],low[N],idx,stk[N],top,scc[N],c;
bool inc[N];
void dfs(int u)
{
dfn[u] = low[u] = ++idx,stk[++top] = u,inc[u] = 1;
for(int i = head[u];i;i = nxt[i])//建 DFS 树且维护 low 数组
{
int v = to[i];
if(!dfn[v])//树边
{
dfs(v);
low[u] = min(low[u],low[v]);
}
else if(inc[v]) low[u] = min(low[u],dfn[v]);//非树边
}
if(dfn[u]==low[u])//是一个顶点,构成一个 SCC
{
c++;//SCC个数+1
while(stk[top]!=u)
inc[stk[top]] = 0,scc[stk[top]] = c,top--;
inc[u] = 0,scc[u] = c,top--;
}
}
求 SCC 有什么用呢?
- 在一些与连通性有关的问题中,我们把每个 SCC 缩成一个点,可以得到一个 DAG,因为如果还环的话就能形成一个新的 SCC。这样就可以在 DAG 上做 DP 之类来解决问题;
- 在 2-SAT 问题(有 \(m\) 个二元方程,并且每个未知数都是 \(0\) 或 \(1\))中,我们求解和判无解也需要用到 SCC,有兴趣的可以自己去看。
Part.3 Tarjan 求割边、边双
在无向图中,定义割边(也叫桥)为删掉这条边后图的连通性改变的边。边双连通分量(简称边双),即为不存在割边的极大子图,与 SCC 一样,具有传递性。
和求 SCC 一样,我们还是去建立一颗 DFS 树。我们重新定义 \(low_x\) 为 \(x\) 号节点最多通过一条非树边能跳到的 DFS 序的最小值。那一条连接 \(u,v\) 的边是桥当且仅当 \(dfn_u<low_v\),就相当于 \(v\) 经过一条非树边到不了 \(u\),这样肯定是割边。
至于求边双,就是每次选一个点,在不经过桥的前提下能到的所有点就形成了一个边双。
一个细节就是边表的 cnt 记得赋值为 \(1\),方便求反向边。
代码和求 SCC 差不多,具体如下:
int n,m,dfn[N],low[N],t;
bool brg[M];
void dfs(int u,int ine)//求割边
{
dfn[u] = low[u] = ++t;
for(int i = head[u];i;i = nxt[i])
{
if(i==ine) continue;//不往回走
int v = to[i];
if(dfn[v]==0)
{
dfs(v,i^1);
low[u] = min(low[u],low[v]);
if(low[v]>dfn[u]) brg[i] = brg[i^1] = 1;//是割边
}
else low[u] = min(low[u],dfn[v]);
}
}
bool vis[N];
int c;//记录边双的个数
void dfs(int u)
{
ans[c].push_back(u),vis[u] = 1;
for(int i = head[u];i;i = nxt[i])
{
if(brg[i]) continue;//不经过桥
int v = to[i];
if(!vis[v]) dfs(v);
}
}
Part.4 Tarjan 求割点、点双
参照割边、边双的定义,割点为删掉这个点后图的连通性改变的点。点双连通分量(简称点双),即为不存在割点的极大子图。但是点双不具有传递性,所以点双是最难的。画一个图就知道了:

左边 \(4\) 个点是个点双,右边 \(4\) 个点也是点双,但是整张图却不是点双,因为中间的点是个割点。
仍然建出 DFS 树,发现用顶点找出一个点双已经不可行了,因为一个点能在多个点双当中。

我们发现,两个点双至多有一个交点,这个点就是割点。如上图,这个交点一定在下面的点双中 \(dfn\) 是最小的,而上面的点双可以由另外的点(类似树根)求出。
所以,对于一条边 \((u,v)\),如果满足 \(low_v\ge dfn_u\),就说明找到一个点双了。
上代码:
int n,m,dfn[N],low[N],c,stk[N],top,t;
vector<int> ans[N];
void dfs(int u,int fa)
{
dfn[u] = low[u] = ++t;
stk[++top] = u;
int son = 0;
for(int i = head[u];i;i = nxt[i])
{
int v = to[i];
if(v==fa) continue;
if(dfn[v]==0)
{
++son;
dfs(v,u);
low[u] = min(low[u],low[v]);
if(low[v]>=dfn[u])//找到点双
{
++c;
while(stk[top]!=v) ans[c].push_back(stk[top--]);
ans[c].push_back(v);--top;ans[c].push_back(u);//细节:不能将点 u 弹出栈
}
}
else low[u] = min(low[u],dfn[v]);
}
if(fa==0&&son==0) ans[++c].push_back(u);//特判:单独一个点也是点双
}
点双有一个巨大的应用——圆方树。
圆方树,就是对于每个点双,新建一个方点,断掉点双原图中的边,然后点双里所有的点向这个方点连边。这样形成的结构就是一棵树。举个例子:

这样,图上问题就变成了树上问题,是不是简单很多?
那圆方树还有什么应用呢?
求 可行路径/必经点 的一些东西。可行路径就对应圆方树上两点的路径加上与树上路径的方点直接相连的点,必经点就是树上路径的圆点;
仙人掌图:比如 P5236,通过重新定义边权,将仙人掌上最短路变为树上路径。
顺便提一句,图上问题就变成了树上问题大多都要判断两点 LCA 为方点的情况。
Part.5 总结
Tarjan 算法在图论问题中有大用,特别是有关联通性的题。
写字不易,给个赞吧~
\]
Tarjan 算法——图论学习笔记的更多相关文章
- 图论学习笔记·$Floyd$ $Warshall$
对于图论--虽然本蒟蒻也才入门--于是有了这篇学习笔记\(qwq\) 一般我们对于最短路的处理,本蒟蒻之前都是通过构建二维数组的方式然后对每两个点进行1次深度或者广度优先搜索,即一共进行\(n\)^2 ...
- 串的应用与kmp算法讲解--学习笔记
串的应用与kmp算法讲解 1. 写作目的 平时学习总结的学习笔记,方便自己理解加深印象.同时希望可以帮到正在学习这方面知识的同学,可以相互学习.新手上路请多关照,如果问题还请不吝赐教. 2. 串的逻辑 ...
- BZOJ 2120 数颜色&2453 维护队列 [带修改的莫队算法]【学习笔记】
2120: 数颜色 Time Limit: 6 Sec Memory Limit: 259 MBSubmit: 3665 Solved: 1422[Submit][Status][Discuss] ...
- Tarjan算法【阅读笔记】
应用:线性时间内求出无向图的割点与桥,双连通分量.有向图的强连通分量,必经点和必经边. 主要是求两个东西,dfn和low 时间戳dfn:就是dfs序,也就是每个节点在dfs遍历的过程中第一次被访问的时 ...
- BZOJ 2038: [2009国家集训队]小Z的袜子(hose) [莫队算法]【学习笔记】
2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec Memory Limit: 259 MBSubmit: 7687 Solved: 3516[Subm ...
- C4.5算法的学习笔记
有日子没写博客了,这些天忙着一些杂七杂八的事情,直到某天,老师喊我好好把数据挖掘的算法搞一搞!于是便由再次埋头看起算法来!说起数据挖掘的算法,我想首先不得的不提起的就是大名鼎鼎的由决策树算法演化而来的 ...
- 「Manacher算法」学习笔记
觉得这篇文章写得特别劲,插图非常便于理解. 目的:求字符串中的最长回文子串. 算法思想 考虑维护一个数组$r[i]$代表回文半径.回文半径的定义为:对于一个以$i$为回文中心的奇数回文子串,设其为闭区 ...
- 算法图解学习笔记01:二分查找&大O表示法
二分查找 二分查找又称折半查找,其输入的必须是有序的元素列表.二分查找的基本思想是将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止:如果x<a[ ...
- C++ OI图论 学习笔记(初步完结)
矩阵图 使用矩阵图来存储有向图和无向图的信息,用无穷大表示两点之间不连通,用两点之间的距离来表示连通.无向图的矩阵图是关于主对角线对称的. 如图所示: 使用dfs和bfs对矩阵图进行遍历 多源最短路径 ...
- vector刘汝佳算法入门学习笔记
//*****-*-----vector***/////// 常用操作封装,a.size();可以读取大小 a.resize();可以改变大小: ...
随机推荐
- LibModbus库开发笔记(一):libmodbus库介绍、编译和基础工程模板
前言 本文章讲解libmodbus. libModbus介绍 libmodbus是一个免费软件库,可根据Modbus协议发送/接收数据.该库用C编写,并支持RTU(串行)和TCP(以太网) ...
- 【Azure 环境】标准版 Logic App 如何查看 Workflow的执行成功数和失败数的指标呢?
问题描述 在Azure中创建逻辑应用(Logic App),有两种计划类型.一是消费型,另一种是标准型. 在消费型的Logic App Metrics页面中,我们可以看见Workflow的执行成功数指 ...
- 解决celery与django结合后,分别启动celery和django的进程同时调用定时任务的问题
django中引入celery后发现在代码中写如下这样的定时任务,启动celery和django的工程后,他们都会调用这个定时任务导致,任务有的时候会冲突出现奇怪的问题.如何解决请继续看. sched ...
- 视觉slam十四讲CH4 ---李群与李代数求导
视觉slam十四讲 ---CH4 李群与李代数求导 李群与李代数相较于CH3是比较的抽象的数学知识,这个工具的提出目的是解决一些旋转位姿描述的优化问题.本讲最终的目的是解决如何描述对旋转求导的问题. ...
- Java 类的成员之四: 代码块(或初始化块)
1 package com.bytezreo.block; 2 3 /** 4 * 5 * @Description 类的成员之四: 代码块(或初始化块) 6 * @author Bytezero·z ...
- Java //在150之内 是三的倍数 输出Zzz 是5个倍数输出 Lll 是7的倍数输出zlzl
1 //在150之内 是三的倍数 输出Zzz 是5个倍数输出 Lll 是7的倍数输出zlzl 2 int i =1; 3 for(i = 1; i<=150;i++) 4 { 5 System. ...
- 关于Chrome版本太旧的更新问题
•问题 这两天不知道咋了,Chrome 老是给我提示版本太旧,需要更新. 作为一名资深的强迫症患者,这让我很是不爽. •解决方案 在桌面找到 Chrome 图标,右击选择[属性],在该位置添加如下语句 ...
- Dyno File Utils - VSCode Extension 新建目录 新建文件 很好用
Dyno File Utils - VSCode Extension 新建目录 新建文件 很好用 快捷键 绑定了 ctrl + n
- vetur 和 volar 不要一起装 - vscode插件 已解决
vetur 和 volar 不要一起装 - vscode插件 会有各种稀奇古怪的问题. 解决方案 利用 vscode 工作区 新建工作区 然后全局 将 volar 禁用工作区,起一个新的vue3项目, ...
- HTML <nav> 标签
定义和用法 标签定义导航链接的部分. 提示和注释 提示:如果文档中有"前后"按钮,则应该把它放到 元素中. 实例 <!DOCTYPE html> <html> ...