首页 > 编程语言 >Tarjan 算法——图论学习笔记

Tarjan 算法——图论学习笔记

时间:2024-03-30 16:59:00浏览次数:13  
标签:Tarjan 点双 int top 图论 SCC 算法 dfn low

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 算法在图论问题中有大用,特别是有关联通性的题。

写字不易,给个赞吧~

\[\text {THE END} \]

标签:Tarjan,点双,int,top,图论,SCC,算法,dfn,low
From: https://www.cnblogs.com/pyy51316/p/18105712

相关文章

  • 莫队算法学习笔记
    Part.1引入当你遇到一个区间询问但是难以用线段树等log算法维护的时候怎么办?那就是——莫队!莫队这个东西能支持区间修改、区间查询的操作,但是这种算法要求离线。莫队有很多种,详细请看下文。Part.2普通莫队我们先来看一道例题(P1972的削弱版):给你一个长度为\(n\)的序列......
  • KMP算法
    对于字符串“abababca”,它的next如下表所示:voidget_next(SStringT,int*next){inti=1,j=0;next[1]=0;//next[1]的值总是0while(i<T.length){if(j==0||T.ch[i]==T.ch[j]){//如果j处于0位或者,俩字符相等++i......
  • 基于DWT(离散小波变换)的图像加密水印算法,Matlab实现
           博主简介:专注、专一于Matlab图像处理学习、交流,matlab图像代码代做/项目合作可以联系(QQ:3249726188)       个人主页:Matlab_ImagePro-CSDN博客       原则:代码均由本人编写完成,非中介,提供有偿Matlab算法代码编程服务,不从事不违反涉及学术原则......
  • 代码随想录算法训练营第六十天|84.柱状图中最大的矩形
    84.柱状图中最大的矩形刷题https://leetcode.cn/problems/largest-rectangle-in-histogram/description/文章讲解https://programmercarl.com/0084.%E6%9F%B1%E7%8A%B6%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E7%9F%A9%E5%BD%A2.html视频讲解https://www.bilibili.com......
  • 代码随想录算法训练营总结
    刷题收获:    通过算法训练营一刷,熟悉并上手实现了一些算法,代码能力得到了很大的提升,也对提高了Java的熟练度,为研究生阶段参加算法竞赛打下了不错的基础。    并且这种每日打卡的形式,能够强制性让自己每天看算法题,收获自然颇丰,也会有助手大佬帮我解决盯了四个......
  • 面了字节 NLP 算法工程师(含大模型方向),跪了。。。
    节前,我们星球组织了一场算法岗技术&面试讨论会,邀请了一些互联网大厂朋友、参加社招和校招面试的同学,针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。汇总合集:《大模型面试宝典》(2024版)发布!......
  • C语言查找-----------BF算法&&KMP算法
    1.问题引入有一个主字符串,有一个子字符串,要求我们寻找子字符串在主字符串里面开始出现的位置;2.BF算法BF算法就是暴力算法,这个做法虽然效率不高,但是按照我们传统的思路依然能够得到结果,接下来我们使用C语言实现这个查找的过程;#include<stdio.h>#include<assert.h>#includ......
  • 搜索与图论(二)bfs---以题为例
    给定一个 n×m的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。最初,有一个人位于左上角 (1,1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。请问,该人从左上角移动至右下角 (n,m)处,至少需要移动多......
  • 动画图解:九大经典排序算法详解-算法宝App
    重新整理了一遍排序算法,结合自己开发的算法宝App的录屏,转成webp动画一起分享给大家,适合新手。概述时间复杂度(timecomplexity)用来描述算法的运行时间。常用大O符号表述。比如:O(n),O(1),O(logn),O(n2)等。举例:O(n)表示线性级复杂度,表示时间复杂度和元素element数量n成正比。......
  • 京东一面挂在了CAS算法的三大问题上,痛定思痛不做同一个知识点的小丑
    写在开头在介绍synchronized关键字时,我们提到了锁升级时所用到的CAS算法,那么今天我们就来好好学一学这个CAS算法。CAS算法对build哥来说,可谓是刻骨铭心,记得是研二去找实习的时候,当时对很多八股文的内容浅尝辄止,很多深奥的知识点只是知道个概念,源码看的也不深,代码量也不够,京东一......