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

讲的很好的教程:链接

什么是强连通:强连通是有向图中的一个概念,如果一个有向图的任意两点间可以互相到达,那么这个有向图就是一个强连通图。
什么是弱连通:弱连通也是有向图中的一个概念,如果一个有向图的有向边全部变成无向边后图是连通的,那么这个有向图就是一个弱连通图。(好像强连通图都是弱连通图?)
什么是强连通分量:强连通分量就是一个图中强连通的一部分。

时间复杂度:Tarjan算法求强连通分量主要依赖于DFS,如果用邻接矩阵存图,那么时间复杂度是O(n^2)的,如果用邻接表存图,那么时间复杂度是O(n+m)的。

在实现算法之前要先明确几个概念:
什么是时间戳:时间戳是用来标记图中每个节点在进行深度优先搜索时被访问的时间顺序,当然,你可以理解成一个序号(这个序号由小到大),用 dfn[x] 来表示。
什么是搜索树:在无向图中,我们以某一个节点 x 出发进行深度优先搜索,每一个节点只访问一次,所有被访问过的节点与边构成一棵树,我们可以称之为“无向连通图的搜索树”。
什么是追溯值:追溯值用来表示从当前节点 x 作为搜索树的根节点出发,能够访问到的所有节点中,时间戳最小的值 —— low[x]。

算法实现比较简单,就是从一个点开始进行DFS,然后沿途记录所有点的时间戳和追溯值并把点入栈,然后在回溯的时候更新路径上各点的追溯值,最后判断搜索起点的时间戳和追溯值,如果它的时间戳和追溯值相同,说明这个点无法通过它的子孙到达时间戳更小的点,那么就说明这个点是图中某个强连通分量的顶点,然后我们只需要出栈至该点也出栈,所有出栈的点就构成一个强连通分量(是个环吗?)。

Tarjan算法求有向图的强连通分量_第1张图片
上图是遍历过一遍某个图后的各点的时间戳和追溯值,前面的是时间戳,后面的是追溯值。

下面是用邻接矩阵的算法实现:

#include
#include
#include 
using namespace std;
const int N = 1e3+5;
bool g[N][N];//邻接矩阵存有向图 
stack<int> st;//装点的序号 
int time=1;//时间戳
int dfn[N];//记录各点的时间戳
int low[N];//记录各点的追溯值 
bool vis[N];//记录某点在不在栈中 
int n,m,a,b;//n为点的数量,m为边的数量
void dfs(int x)
{
	st.push(x);//访问过的点扔进栈里
	vis[x]=1;//记录x在栈中 
	dfn[x]=low[x]=time++;//给时间戳和追溯值赋值
	for(int y=1;y<=n;++y)
	{
		if(g[x][y])//如果从x可以到y 
		{
			if(dfn[y]==0)//如果y这个点还没被访问过 
			{
				dfs(y);//以y为树根再往下搜索 
				low[x]=min(low[x],low[y]);//更新追溯值 
			}
			else if(vis[y])//如果y已经在栈中 
			{
				low[x]=min(low[x],low[y]);//直接更新时间戳就行了 
			}
		}
	}
	if(dfn[x]==low[x])//如果一个点的时间戳==追溯值,说明这个点无法通过子孙再到达时间戳更小的点,就说明这个点是强连通分量的一个顶点,那就出栈至这个元素,出栈的元素就形成一个强连通分量(可以是单点,可以找环?) 
	{
		while(st.top()!=x)
		{
			cout<<st.top()<<" ";
			vis[st.top()]=0;//不在栈里了记得标记 
			st.pop();
		}
		cout<<st.top()<<endl;
		vis[st.top()]=0;//不在栈里了记得标记 
		st.pop();
	} 
}
int main()
{
	cin>>n>>m; 
	for(int i=1;i<=m;++i)
	{
		cin>>a>>b;
		g[a][b]=1;//有向图 
	}
	for(int i=1;i<=n;++i)
	{
		if(dfn[i]==0)//时间戳是0表示该点还没被访问过
		{
			dfs(i);//没访问过就从这点开始访问,该点为搜索树的树根 
		} 
	}
	return 0;
}
/*
7 9
1 2
2 3
3 5
3 4
4 5
4 2
1 6
6 7
7 1

5
4 3 2
7 6 1

7 9
5 2
2 3
3 1
3 4
4 1
4 2
5 6
6 7
7 5

1
4 3 2
7 6 5
*/

你可能感兴趣的:(SDNUOJ,c++,Tarjan算法,求强连通分量,图论)