Tarjan算法之求强连通分量

最近又学习了强连通分量的Tarjan求法,先是看了别人的许多博客,才勉勉强强看懂,自己写完博客后感到十分显然,也没有表面上看的那么高大上。

好了,转入正题,先说说什么是强连通分量:

有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。----摘自百度百科。

简单来说,就是一个有向图中,有一个子图,子图中的任意两个点都能互相到达,则这个子图就是整个图的强联通分量,这么来说,显然的是,整张图一定是被分割成了几个强连通分量,我们举个例子看一下:

显而易见的1,2,3号节点是可以互相到达的,所以1,2,3组成的子图是强联通分量,此外,5和4号节点单独成为一个强连通分量,于是,这张图就有了三个强连通分量至于怎么微妙地求出这几个强连通分量,这里用的是Tarjan算法,看完之后就会恍然大悟的。

首先,我们定义一个数组,dfn[],这个数组存的是当前节点的时间戳(什么是时间戳?时间戳就是当前节点的DFS序,也可以理解为第一次遍历到当前节点的时间),还有low[]数组,这个数组存的是当前节点能够追溯到的时间戳最小的节点的时间戳,也就相当于这个节点所在的强连通分量的“根节点”。可能有点难理解,多读几遍,说不定能理解一点。

那么low【】数组和dfn【】数组要如何维护呢?

dfn【】数组,显然,在遍历到该节点时记录,以后就不用动了,而low【】数组,是在扩展点时更新的,比如说,当前节点为u,将要扩展的点为v,若dfn[v]==low[v]&&vis[v]==false,也就是说,v点是可以到达u点的(因为u点没有被访问结束,说明u点是由v点扩展出来的),这样就出现了一个环。!!!注意!!!有环就有强连通分量,想想是不是?环上的所有的点组成了一个强连通分量,之后只要vis[u]=false,low[u]=low[v],渐渐地回溯并找其他的点即可

我们还是以上图为例:

假设从1点开始遍历,dfn[1]=1,low[1]=1,继续2点,dfn[2]=2,low[2],继续3点,dfn[3]=3,low[3]=3,继续深搜,发现下一个点是1,有趣,满足条件dfn[v]==low[v]&&vis[v]==false,修改low【3】=1,且没有要拓展的点,回溯。边回溯边修改,low【2】=1,继续回溯,到一,访问结束。接着找其他点,继续访问。最后访问的结果应该是这样的:

i           :1,2,3,4,5。

dfn【】:1,2,3,4,5。

low【】:1,1,1,4,5.

所以一共有三个强连通分量,分别是以1,4,5为根的。代码贴上(大佬勿喷):

#include <bits/stdc++.h>

using namespace std;

int dfn[10010],low[10010];
bool vis[10010];
vector<int> son[10010];
int n,m;
int tim=0;
int ans=0;

template <typename T> void read(T &x) {
	x = 0; char c = getchar ();
	for (; !isdigit(c); c = getchar ());
	for (; isdigit(c); c = getchar ()) x = x * 10 + c - '0';
}

void Tarjan(int x){
	vis[x]=true;
	tim++;
	dfn[x]=low[x]=tim;
	for (int i=0;i<son[x].size();i++){
		int v=son[x][i];
		if (!vis[v]) Tarjan(v);
		if (vis[v]) low[x]=min(low[x],low[v]);
	}
	if (dfn[x]==low[x]) ans++;
}

int main() {
	cin>>n>>m;
	for (int i=1;i<=m;i++){
		int x,y;
		read(x);
		read(y);
		son[x].push_back(y);
	}
	for (int i=1;i<=n;i++)
		if (!vis[i]) Tarjan(i);
	for (int i=1;i<=n;i++)
		cout<<low[i]<<' ';
	cout<<endl;
	cout<<ans<<endl;
	return 0;
}

蒟蒻个人理解,有意见可以提。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值