图的连通性问题及Tarjan算法

推荐观看视频教程

一、图的连通性

1. 无向图的连通性

在无向图G=(V,E)中:

连通:若节点u和v能够互相到达,则称u,v是连通的;

连通图:若图中任何节点之间都是可互相到达的,则称G是连通图,否则称G是非连通图;

这是一个连通图这不是一个连通图,

③连通分量(连通分支):我们可以看出上面右图有两个部分连通的图,叫做连通分量(或连通分支)

④无向图的连通性判定:并查集、DFS、BFS、WARSHALL 算法;

⑤非连通的无向图求连通分量:可通过额外设置计数器count(初始值0)统计出图的连通分量,每调用dfs(或bfs)遍历一次,计数器count增1。当遍历完无向图时,若count=1,则图连通,若count>1,图非连通,count的值就是该图的连通分量数。

需要注意的是,连通分量的提出是以"整个无向图不是连通图"为前提的,因为如果无向图是连通图,则其无法分解出多个最大连通子图,因为图中所有的顶点之间都是连通的。

2.有向图的连通性

①强连通:在有向图中,若节点 和节点 能互相到达(不必是同一路径),则称 u,强连通;

节点1可以通过(1,2,3)到达3,节点3可以通过(3,4,1)到达1,所以3和1是强连通;

强连通图:在有向图 G=(V,E) 中,若对于中任意两个不同的顶点 和 v,都存在从 到 以及从 到 的路径,则称 G是强连通图。

这是一个强连通图

强连通分量SCC(strongly connected components)非强连通图的极大强连通子图,称为强连通分量。强连通图只有一个强连通分量,即是其自身;非强连通的有向图有多个强连分量。

这个有向图中有两个强连通分量(2,3,1)和(7,6,4,5)。

小提示:“连通图” 是在无向图 的基础上对图中顶点之间的连通做了更高的要求,而强连通图” 是在有向图 的基础上对图中顶点的连通做了更高的要求。

④有向图的连通性判定算法:

方法一:可以调用DFS搜索 V 次,V是顶点的个数,就是对每个顶点都做一次DFS搜索,判断是否可达。这样的复杂度为O(V*(V+E))。

方法二:可以参考求解连通分量的算法Tarjan算法,我们可以在O(V+E) 的时间内找到所有的连通分量,如果连通分量的个数为1,则说明该图是强连通的。

⑤有向图求强连通分量算法:Kosaraju 算法Tarjan算法

二、Tarjan算法

Tarjan(音译为塔扬)算法的核心其实是dfs,主要是通过维护两个数组来完成强连通分量的计算:

dfn[]数组:负责记录每个节点的dfs顺序简称为dfs序

low[]数组:负责记录从每个节点的出发沿可行边所能到达的点中dfs序号最小的序号。

第一个数组好理解:

对于下面左图的有向图进行dfs,并产生dfs序应该是右边的结果

第二个数组费解一点:

 

P2863 [USACO06JAN]牛的舞会

//P2863 [USACO06JAN]牛的舞会The Cow Prom
#include <cstdio>
#include <stack>
using namespace std;
const int maxn=10005;
const int maxm=50005;
struct Edge{
	int v,w,nxt;
}edge[maxm];
int n,m,cnt=1,head[maxn],instack[maxn];
int scctot;
int index;
int dfn[maxn];///节点的dfs序 
int low[maxn];///dfs时,节点能到达的最小dfs序 
int scc[maxn];///每个节点所属的强连通分量 
int num[maxn];///记录每个强连通分量中节点个数 
stack<int>s;
inline void adedge(int u,int v,int w)
{
	edge[++cnt].v=v,edge[cnt].w=w,edge[cnt].nxt=head[u],head[u]=cnt;
}
void tarjan(int u)
{
	if (dfn[u])return;
	index++;
	dfn[u]=low[u]=index;
	instack[u]=1;
	s.push(u);
	for (int i=head[u];i;i=edge[i].nxt)
	{
		int v=edge[i].v;
		if (!dfn[v])///如果没有dfs过,则继续深搜 
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
			///dfs返回后更新当前节点的low值等于当前节点能到达的节点中low值最小的low值 
		}
		if (instack[v])
		///如果v的dfs序已存在,说明从节点u可以回到dfs序更小的节点,或者说已形成了一个环 
		{			
			low[u]=min(low[u],dfn[v]);
			///当前节点u的low值应该等于当前节点能到达的节点中low值最小的low值
		}		
	}
	if (dfn[u]==low[u])///找到一个强连通分量
	{
		scctot++;///强连通分量个数加1 
		while(1)
		{
			int t=s.top();
			scc[t]=scctot;
			num[scctot]++; 
			instack[t]=0;
			s.pop();
			if (t==u)break;
		}
	} 
}
int main()
{
	scanf("%d %d",&n,&m);
	for (int i=1,u,v;i<=m;++i)
	{
		scanf("%d %d",&u,&v);
		adedge(u,v,1);
	}
	for (int i=1;i<=n;++i)
	{
		if (!scc[i])tarjan(i);///如果有节点没有生成所属强连通分量值 
		//if (!dfn[i])tarjan(i);///如果有节点没有dfs访问到 
	}
	int ans=0;
	for (int i=1;i<=scctot;++i)///统计节点个数大于1的强连通分量的个数 
	{
		if (num[i]>1)ans++;
	}
	printf("%d",ans);
	return 0;
}

三、 点集和割集

对于图的连通性而言,不同节点或边的“重要性”是不同的,比如在通信网络中,有的节点或边出现中断,会对整个连通性的影响到头重要,而有的则不影响全局。另外,也存在一部分节点联合起来,从而对于整个图的连通性起关键作用。

一句话:点集和割集主要是为了研究,一些点的存在或者删除后对整个图的连通性影响。