浅谈 Tarjan 算法

news2025/2/27 2:08:55

在了解 Tarjan 算法之前,我们先来了解 dfs 搜索树。

1 dfs 生成树

定义: dfs 遍历整张图,按照 dfs 序构成一棵树。

1.1 有向图的 dfs 生成树

在这里插入图片描述

有向图的 dfs 生成树包括四种边:

  • 树边(tree edge):图中黑色边表示。表示搜索访问到未访问过的结点。
  • 回边(back edge):图中橙色边表示。表示该边指向先前访问过的祖先。
  • 叉边(cross edge):图中蓝色边表示。表示该边指向先前访问过的非祖先结点。
  • 前向边(forward edge):图中红色边表示。表示该边指向先前访问过的孩子结点。

1.2 无向图的 dfs 生成树

在这里插入图片描述
无向图的 dfs 生成树仅包含两种边:树边和回边。

证明没有叉边和前向边:

  • 如果存在叉边 u → v u \to v uv,其中 u u u 为当前搜索访问到的结点, v v v 为之前访问过的非祖先结点,那么在访问 v v v 时,就应该访问了 u u u,与 u u u 为当前搜索访问到的结点矛盾。

  • 如果存在前向边 u → v u \to v uv ,那么在首次访问 v v v 时,该边就会作为一条回边指向 u u u ,不会作为 u → v u \to v uv 一条前向边。


2 Tarjan 算法

2.1 桥

题目

2.1.1 桥的定义

无向图中,若删去一条边会使得这个图的极大连通分量数增加,则该边被称为桥。

也可以理解为无向图的一个连通块中,若删除一条边会使得至少两点之间无法相互到达,该边被称为桥。

2.1.2 实现

样例:
在这里插入图片描述
样例中, 1 → 2 1 \to 2 12 5 → 6 5 \to 6 56 就是该图的桥。

  • 容易发现,只有 树边 才可能作为桥,而 dfs 生成树中红色的回边 4 → 2 4 \to 2 42 标记了 2 → 3 → 5 → 4 2 \to 3 \to 5 \to 4 2354 沿途上的树边都不可能作为桥。因此我们得出结论:一条边是桥,当且仅当它是 不被回边标记的树边

在 Tarjan 算法中,我们对每个点 u u u 按 dfs 序打上时间戳 d f n u dfn_u dfnu,同时记录 l o w u low_u lowu 表示 u u u 不经过其父亲能到达的最小时间戳,即按照 dfs 生成树中有向边能到达的最小时间戳。

那么一条树边 u → v u \to v uv 不被回边标记的充要条件即为: l o w v > d f n u low_v > dfn_u lowv>dfnu。如例中, 1 → 2 1 \to 2 12low[2]=2,dfn[1]=1,可以得到 1 → 2 1 \to 2 12 就是一座桥。

void Tarjan(int u, int fa)
{
	dfn[u] = low[u] = ++ idx;
	for(auto v : G[u])
	{
		if(v == fa) continue;
		
		if(!dfn[v]) // tree edge 
		{
			Tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if(low[v] > dfn[u]) // is bridge
				bridge[u][v] = 1;
		}
		
		else // back edge 
			low[u] = min(low[u], dfn[v]);
	}
}

2.2 割点

题目

2.2.1 割点的定义

无向图中,若删去一个点会使得这个图的极大连通分量数增加,这个点被称为割点。

也可以理解为无向图的一个连通块中,若删除一个点会使得至少两点之间无法相互到达,该点被称为割点。

2.2.2 实现

在这里插入图片描述
首先,由于图不连通,我们大体的决策是将图分成若干连通块,在每个连通块对应的 dfs 生成树上进行搜索。

  • 对于结点 5 5 5,删去 5 5 5 显然不会对儿子结点 3 , 4 3,4 3,4 造成影响,因为它们有回边可以连到结点 5 5 5 的祖先,而对于儿子结点 6 6 6 会有影响,其没有回边可以连到可以连到结点 5 5 5 的祖先。

    我们推广到一般情况:对于结点 u u u ,若存在儿子结点的 l o w v ≥ d f n u low_v \ge dfn_u lowvdfnu ,即 v v v 没有回边可以连到结点 u u u 的祖先,那么 u u u 一定是个割点。

  • 然而 1 1 1 号虽然满足上述条件,然而 1 1 1 并不是割点。

    因此我们还需判断一种情况,即 u u u 作为了 dfs 搜索树的根节点,不论如何其任何儿子 v v v l o w v low_v lowv 都不可能小于 d f n u dfn_u dfnu ,因此需要分开讨论:当 u u u 作为根节点时,若 u u u 有两个及以上的儿子时, u u u 为割点。

int dfn[N], low[N], idx, cnt;
bool cut[N];
void Tarjan(int u, int fa)
{
	dfn[u] = low[u] = ++ idx;
	
	int son = 0;
	
	for(auto v : G[u])
	{	
		if(v == fa) continue;
		
		if(!dfn[v])
		{	
			Tarjan(v, u);
			low[u] = min(low[u], low[v]);
			
			son ++ ;
			if(fa != 0 and low[v] >= dfn[u] and !cut[u]) cnt ++ , cut[u] = 1;
		}
		
		else low[u] = min(low[u], dfn[v]);
	}
	
	if(fa == 0 and son >= 2 and !cut[u]) cnt ++ , cut[u] = 1;
	
}

代码

2.3 强连通分量

题目

2.3.1 强连通分量的定义

注意,强连通分量(SCC,Strongly Connected Components)是在单向图中的。

强联通子图,定义为:在 单向图 中,该子图上的任意两点之间能互相到达。

强联通分量,定义为: 极大连通子图

2.3.2 实现

以下图为例:

在这里插入图片描述

该例中强连通分量为 1 , 2 , 3 , 4 , 5 , 6 , 7 1,2,3,4,5,6,7 1,2,3,4,5,6,7

  • u u u 是当前 scc 第一个被访问的结点,那么该 scc 的结点一定在 dfs 生成树中以 u u u 为根的子树内。

    若存在该 scc 中的 v v v 不在子树内,则 u u u v v v 的路径中一定存在叉边或回边,根据叉边、回边的定义,则 v v v u u u 之前已经被访问过,与 u u u 是当前 scc 第一个被访问的结点矛盾。

  • 如果我们把单个节点也算作是强连通分量,那么所有 scc 的根应 u u u 当都满足 l o w u = d f n u low_u=dfn_u lowu=dfnu

具体实现

考虑使用一个栈 s s s 存储遍历到的结点,每当访问到一个 l o w u = d f n u low_u=dfn_u lowu=dfnu 的结点,就将栈内元素从栈顶 弹出直至 u u u ,这些结点即为以 u u u 为根的强连通分量。

在遍历 u u u 的子树过程中,确实有可能会遍历到其他强连通分量的点,根据树的结构,一定会先遍历到其他 scc 的根 v v v,而在遍历 v v v 的过程中,发现了 l o w v = d f n v low_v=dfn_v lowv=dfnv ,便会将栈内的元素弹出直到 v v v,因此在遍历完 u u u 的子树后仍然留在栈中的元素一定是以 u u u 为根的 scc 中的元素。

而在搜索过程中,我们会遍历到以下三种结点:

  • 未访问的结点:该边为树边,将 v v v 入栈,对 v v v 进行 dfs,更新 l o w low low

  • 访问过并且不在栈中:该边为叉边或前向边,说明已经是其他 scc 中的结点,对当前 scc 不会产生任何影响,忽略。

  • 访问过并且在栈中:该边为叉边、前向边或回边,更新 l o w low low 值。

int low[N], dfn[N], idx;
bool ins[N];
int tot, belong[N];

stack <int> s;

void Tarjan(int u)
{
	low[u] = dfn[u] = ++ idx;
	
	s.push(u); ins[u] = 1;
	
	for(auto v : G[u])
	{
		if(!dfn[v])
		{
			Tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		if(dfn[v] and ins[v]) 
			low[u] = min(low[u], dfn[v]);
	}
	
	if(low[u] == dfn[u]) // 当前 scc 的根
	{
		++ tot;
		while(s.top() != u) // 出栈,标记结点所属 scc
		{
			int x = s.top(); s.pop();
			belong[x] = tot, ins[x] = 0;
		}
		s.pop(); belong[u] = tot, ins[u] = 0;
	}
	
}

代码

2.3.3 强连通分量缩点

在无向图的一些问题中,若将所有 scc 合并为一个点,那么这张无向图就会变成一张有向无环图(DAG,Directed Acyclic Graph)。

因此我们只需保留连接两个不同 scc 的边来构成缩点后的新图。

	for(int i=1; i<=n; i++)
	{
		if(!dfn[i])
			idx = 0, Tarjan(i);
	}
	
	for(int u=1; u<=n; u++) // 缩点后的图 e
	{
		for(auto v : G[u])
		{
			int x = belong[u], y = belong[v];
			if(x != y)
				e[x].push_back(y);
		}
	}

[模板] 缩点

题目

由于题目存在环且能多次经过同一点,直接在图上进行搜索显然不可行。

由于点权均为非负数,在经过一个环时,显然应当经过还上的所有点,此时我们考虑缩点,将图变成一张 DAG ,然后用记忆化搜索或者拓扑排序求解最长路径即可。

2.4 双联通分量

无向图中,双联通分量包含点 点双联通分量边双联通分量

2.4.1 双联通分量的定义

  • 点双联通:对于点 u u u v v v ,删去图上任意一个点, u u u v v v 始终连通,则称 u u u v v v 点双联通。

    容易发现,若一个子图点双连通,那么这个子图上一定不存在割点。

  • 边双连通:对于点 u u u v v v ,删去图上任意一条边, u u u v v v 始终连通,则称 u u u v v v 双联通。

    容易发现,若一个子图边双连通,那么这个子图上一定不存在桥。

  • 点双连通分量:极大点双连通子图。

  • 边双连通分量:极大边双连通子图。

2.4.2 边双的实现

题目

在找出原图上的桥后,将其在原图中去掉,剩下的连通块即为边双连通分量。

int cnt; 
bool vis[N];
vector <int> dcc[N];
void dfs(int u)
{
	vis[u] = 1;
	dcc[cnt].push_back(u);
	for(int i=head[u]; i; i=e[i].nxt)
	{
		if(vis[e[i].v] or bridge[i]) continue;
		dfs(e[i].v);
	}
}

代码

2.4.3 边双缩点

我们将每个边双看作一个新节点,原图上的桥看作连接边双之间的边,这样新图会构成一棵树。

for(int i=2; i<=tot1; i++)
	{
		int u = e1[i].v, v = e1[i ^ 1].v;
		int x = belong[u], y = belong[v];
		if(x != y) add_edge2(x, y);
	}

[NOIP2022] 建造军营

题目

在边双中的任意两点之间都不需要保护道路,考虑边双缩点。由于原图为连通图,边双缩点后变为一棵树,进行树 dp 统计答案。

2.4.4 点双的实现

详见圆方树章节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/618705.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CDC是什么?有没有合适的技术方案?

CDC 是 Change Data Capture(变更数据获取)的简称。核心思想是&#xff0c;监测并捕获数据库的 变动&#xff08;包括数据或数据表的插入、更新以及删除等&#xff09;&#xff0c;将这些变更按发生的顺序完整记录下 来&#xff0c;写入到消息中间件中以供其他服务进行订阅及…

阿里、百度、值得买齐发声,电商的“AIGC式”进化

配图来自Canva可画 一年一度618要来了&#xff0c;和往年一样折扣力度、明星直播等话题被炒得火热&#xff0c;不同的是今年618的科技属性更强。 究其原因&#xff0c;过去半年AIGC技术被电商平台应用到实际运营中&#xff0c;“AIGC选品”、“虚拟货场”、“智能客服”成为电商…

《MySQL(六):基础篇- 事务》

文章目录 6. 事务6.1 事务简介6.2 事务操作6.2.1 未控制事务6.2.2 控制事务一6.2.3 控制事务二 6.3 事务四大特性6.4 并发事务问题6.5 事务隔离级别 6. 事务 6.1 事务简介 事务 是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一…

【机器学习】神经网络入门

神经网络 非线性假设 如果对于下图使用Logistics回归算法&#xff0c;如果只有x1和x2两个特征的时候&#xff0c;Logistics回归还是可以较好地处理的。它可以将x1和x2包含到多项式中 但是有很多问题所具有的特征远不止两个&#xff0c;甚至是上万个&#xff0c;如果我们想要…

MySQL数据库给表添加索引

说明&#xff1a;当数据库中的记录数过多时&#xff0c;查询速度会显著变慢。此时可以给表创建索引&#xff0c;提高查询速度。 一、创建索引前 我现在有一张表&#xff0c;有1000万条记录&#xff0c;根据username值&#xff0c;查询一条记录&#xff0c;测试下查询时间&…

赛宁网安助力智能网联汽车发展 | “饶派杯”XCTF车联网安全挑战赛圆满收官

​​ 2023年5月31日&#xff0c;“饶派杯”XCTF车联网安全挑战赛在江西省上饶市圆满落幕。本次大赛特邀国内21支精英战队参与比拼&#xff0c;参赛选手覆盖全国知名高校、自动驾驶汽车和科研院所等车联网安全人才。最终&#xff0c;经过9个小时激烈角逐&#xff0c;来自南京邮电…

chatgpt赋能python:Python自动更新技术的应用

Python自动更新技术的应用 Python是一款高效的编程语言&#xff0c;广泛应用于各种软件开发、数据分析及人工智能等领域。随着大数据和人工智能的快速发展&#xff0c;Python语言的应用也日益普及&#xff0c;更多的企业和个人开始使用Python编写自己的程序。而随着程序的使用…

上榜“网络安全企业科技能力百强”啦!

最新公布的《2023网络安全企业科技能力报告》显示&#xff0c;顶象在“2023网络安全企业科技能力百强”和“2023网络安全企业有效专利数量百强”等两个榜单中均处于前列。 《2023网络安全企业科技能力报告》由中关村网络安全与信息化产业联盟发布&#xff0c;旨在探究网络安全…

微信开发者工具公众号网页项目实现本地项目调试

背景 最近业务场景中有需要微信H5进行实现,需要网页授权,需要用户进行点击授权的操作,跳转一个微信公众号后台设置的授权域名下的网页后才能获取到code,其他网页授权步骤这里不进行展开,不想频繁的打包上传的服务器看实现效果,所以考虑从微信开发者工具中实现本地调试,搜索过相…

如何开发原生的 JavaScript 插件(知识点+写法)

一、前言 通过 "WWW" 原则我们来了解 JavaScript 插件这个东西 第一个 W "What" -- 是什么?什么是插件,我就不照搬书本上的抽象概念了,我个人简单理解就是,能方便实现某个功能的扩展工具.(下面我会通过简单的例子来帮助读者理解) 第二个 W "Why&q…

(9)基于发射器的调优

文章目录 前言 1 概述 2 调优值 3 用任务规划器设置 前言 你可以在飞行中使用你的遥控发射器进行广泛的参数调优。这是为那些无法使用自动调优功能的高级用户准备的&#xff0c;或者希望通过对每个参数的完全手动调优控制来进行微调。 1 概述 基于发射机的调优允许你在飞行…

scala内建控制结构

一、条件表达式 &#xff08;一&#xff09;语法格式 - if (条件) 值1 else 值2&#xff08;二&#xff09;执行情况 条件为真&#xff0c;结果是值1&#xff1b;条件为假&#xff0c;结果是值2。如果if和else的返回结果同为某种类型&#xff0c;那么条件表达式结果也是那种类…

Vue.js 中的 props 和 $emit 方法有什么关系?

Vue.js 中的 props 和 $emit 方法有什么关系&#xff1f; 在 Vue.js 中&#xff0c;props 和 $emit 方法是两个常用的概念。它们可以帮助开发者更方便地实现组件之间的通信。但是这两个概念有什么关系呢&#xff1f;本文将会详细介绍 Vue.js 中的 props 和 $emit 方法&#xf…

【LeetCode每日一题】——1475.商品折扣后的最终价格

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 栈 二【题目难度】 简单 三【题目编号】 1475.商品折扣后的最终价格 四【题目描述】 给你一…

【区块链 | EVM】深入理解学习EVM - 深入了解 Solidity:堆栈

探讨 EVM 堆栈机器,以及如何在堆栈中推入和弹出数据 原文链接: https://betterprogramming.pub/solidity-tutorial-all-about-stack-c1ec6070fe60探讨 EVM 堆栈机器,以及如何在堆栈中推入和弹出数据 图片来源:Iva Rajović on Unsplash 这是"深入Solidity数据存储位置…

A股月份效应 | Python量化A股市场魔咒,5穷6绝7翻身准确吗?| 邢不行

A股有很多广为流传的谚语&#xff0c;它们大多源于投资者对交易经验的总结和共识。 比如我们之前验证过的散户反买别墅靠海、跳空必回补等谚语。 今天我们要验证的&#xff0c;是五穷六绝七翻身这句谚语。 它的意思是A股在5月、6月可能会跌的很惨&#xff0c;到7月会开始反弹回…

浅谈倾斜摄影三维模型数据裁剪的应用场景

浅谈倾斜摄影三维模型数据裁剪的应用场景 倾斜摄影三维模型数据裁剪是将倾斜摄影三维模型数据中不需要的部分删除或隐藏的过程&#xff0c;可以提高数据处理效率和准确性。倾斜摄影三维模型数据裁剪广泛应用于建筑、城市规划、地质勘探、环境监测等领域。本文将介绍倾斜摄影三维…

基于Springboot+Vue的计算机房管理系统

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 计算机房管理系统在现…

读取所在点的像素值

由于项目需求&#xff0c;需要用到开源的软件开发&#xff0c;就开始研究GDAL&#xff0c;这个开源库&#xff0c;目前来说&#xff0c;调用GDAL库&#xff0c;最方便的语言还是python &#xff0c;简单记录下&#xff0c;用python语言做GDAL开发的一些东西吧。一个在开发中经常…

​浅谈Vue3响应式原理与源码解读

一. 了解几个概念 什么是响应式 在开始响应式原理与源码解析之前&#xff0c;需要先了解一下什么是响应式&#xff1f;首先明确一个概念&#xff1a;响应式是一个过程&#xff0c;它有两个参与方&#xff1a; 触发方&#xff1a;数据响应方&#xff1a;引用数据的函数 当数据发…