Tarjan算法与连通性相关(一)

昨天在打牛客多校的时候遇到了一道与连通性有关的图论题,笔者一眼就看出这题考察的知识点是Tarjan算法,但是笔者上次写Tarjan算法还是三年前的事情(太惭愧了qaq),于是笔者和队友在赛时花了一个小时时间学习了Tarjan算法成功通过了此题,故写下这篇博客进一步加深印象,同时也是分享自己对于该算法的理解

什么是Tarjan算法?

Tarjan是一位伟大的美国计算机科学家的名字,他发明了许多好用的算法和数据结构,以他的名字命名,这里我们主要介绍的是与连通性有关的Tarjan算法

有向图强连通分量的Tarjan算法

什么是强连通分量?

首先我们要知道强连通的概念:有向图 G 强连通是指,G 中任意两个结点连通,换句话说就是 G 中任意两个结点可以相互到达,强连通分量指的是从这张有向图中选出一个最大的子图,使得这张子图是强连通的

怎么求强连通分量?

在介绍Tarjan算法求强连通分量前,首先我们要知道一个很重要的概念叫DFS生成树,如图所示:

这颗树是在我们进行DFS的过程中,按照搜索的顺序遍历过程中生成的一颗树,我们会发现这颗树上有4种不同颜色的边:

1.树边(黑色边):每次搜索到一个还未被访问的结点形成的边

2.返祖边(红色边):指向祖先结点的边

3.横叉边(蓝色边):搜索的过程中遇到一个已经访问过的结点形成的边,但该点不是当前结点的祖先

4.前向边(绿色边):搜索过程中遇到子树中的结点形成的边

为什么我们要在这里介绍DFS生成树呢?

我们观察树的结构发现,对于属于某一个强连通分量的结点u,如果在搜索树中它是所在强连通分量第一个被遍历到的点,那么该强连通分量中的剩余点一定在以u为根的子树中,这个性质对我们求出强连通分量具有重要的作用

学习完搜索树后,我们终于可以开始介绍Tarjan算法求有向图强连通分量了

Tarjan算法是基于对图进行深度优先搜索,我们视每个连通分量为搜索树中的一棵子树,在搜索过程中,维护一个栈,每次把搜索树中尚未处理的节点加入栈中

在算法的过程中我们需要维护几个变量:

dfn[u]: u 的dfs序,即在DFS的过程中结点 u 被搜索到的次序

low[u]:在 u 的子树中能够回溯到的最早的已经在栈内的点

我们观察树的结构,很自然能够发现,对于一个强连通分量内的点,它的 low 值要么等于该强连通分量的根结点的 dfn 值,要么是该强连通分量通过一条边
能够到达的结点的 dfn 值

进一步的,我们可以发现对于一颗子树,子树中其它结点的 dfn 值一定会大于根结点的 dfn 值;同时,子树中结点的 low 值一定不小于根结点的 dfn 值

那么维护这两个变量有什么用呢?

我们注意到刚才上面加粗的那句话,很自然能够想到:对于一个强连通分量,有且只有根结点的 dfn 值会等于 low 值,所以我们考虑把当前搜索到的元素加入到一个栈中,不断弹出栈中元素,直到找到 dfn 值和 low 值相等的结点,那么这些点就是以这个 dfn 值和 low 值 相等的点为根的一个强连通分量

所以现在我们的瓶颈就在于如何维护 dfn 和 low 的信息

在搜索的过程中,我们考虑当前的结点 u 以及与它相连的结点 v ,一共有三种情况:

  1. v 未被访问过:继续对 v 进行dfs,在回溯过程中用 low[v] 更新 low[u] ,因为 u 和 v 之间存在连边,所以 v 能够回溯到的点, u 一定能回溯到

  2. v 被访问过且在栈中,那么 low[u]=min(low[u],dfn[v])

  3. v 被访问过且不在栈中,那么说明 v 所在的强连通分量已经被处理,不需要进行操作

综上所述,我们就能够在 O(n+m) 的时间复杂度内求出强连通分量了

代码如下:

int dfn[N],low[N],dfncnt,s[N],in_stack[N],tp;
int scc[N],sc;
int sz[N];
vector<int> g[N];
void tarjan(int u){
	low[u]=dfn[u]=++dfncnt,s[++tp]=u,in_stack[u]=1;
	for(auto v:g[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else {
			if(in_stack[v])
				low[u]=min(low[u],dfn[v]);
		}
	}
	if(dfn[u]==low[u]){
		++sc;
		while(s[tp]!=u){
			scc[s[tp]]=sc;
			sz[sc]++;
			in_stack[s[tp]]=0;
			--tp;
		}
		scc[s[tp]]=sc;
		sz[sc]++;
		in_stack[s[tp]]=0;
		tp--;
	}
}
posted @   isletfall  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
点击右上角即可分享
微信分享提示