Tarjan

有向图的强连通分量
概念:在有向图中,若点a与b间存在路径,则a与b连通,若存在a到b的路径同时存在b到a的路径则成a与b强连通,若一个有向图中任意两点均强连通,则此图为强连通图,一个有向图的极大强连通子图称为强连通分量。
求强连通分量可以转化为求环路及无出度的顶点,使用DFS遍历图可以使得我们更清楚的看清环路及无出度顶点,例如:
Tarjan
图中有两个环,但是可以合并为一个强连通分量,所以只找环和无出度点是不完整的,因此我们可以去找极大连通子图对应子树的根,上图中为顶点1,分析一波,1号顶点的特点是从1号顶点出发的所有路径中可以追溯到的DFS序最小的顶点同样是1,且其他顶点不具备这个特点,因此引入DFN数组保存所有顶点的DFS时序,LOW数组保存所有顶点可以追溯到的最小DFS时序,则1号顶点特点为LOW=DFN,当前问题重点转化为求LOW,LOW的求解是递归的,当前顶点的LOW为其所有孩子顶点LOW的最小值,LOW初始化为DFN,因为最大就是DFN,边界条件为当前顶点无孩子或孩子又是祖先,无孩子时,LOW保持DFN不变,孩子为祖先时,因为祖先孩子的LOW依赖当前的LOW,所以此时要与祖先孩子的DFN比较取最小,找到一个满足条件的根,则输出这颗子树,不会产生干扰。

#include<iostream>
#include<vector>
#include<stack>
#include<string.h>
#define Size 101

using namespace std;

int n,m,DFS_Clock=0;
vector<int> Map[Size];
int LOW[Size],DFN[Size];
stack<int> Stack;
bool InSt[Size];

void CreateMap()
{
	int a,b;
	cin>>n>>m;
	for(int i=1;i<=n;++i) Map[i].clear();
	for(int i=1;i<=m;++i){
		cin>>a>>b;
		Map[a].push_back(b);
	}
}

int Min(int a,int b) {return a<b?a:b;}

void Tarjan(int now)
{
	LOW[now]=DFN[now]=++DFS_Clock;//建立DFS遍历时序DFN,LOW初始化为DFN 
	Stack.push(now);//当前顶点入栈 
	InSt[now]=true;//入栈标记 
	for(int i=0;i<Map[now].size();++i){
		int next=Map[now][i];//枚举now连接的所有顶点next 
		if(!DFN[next]){//如果next还没有遍历到
			Tarjan(next);//递归遍历next,以此建立LOW[next] 
			LOW[now]=Min(LOW[now],LOW[next]);//递归返回LOW[next],以此建立LOW[now] 
		}else if(InSt[next]){//如果next遍历过了且还在栈中,递归的边界之一
			LOW[now]=Min(LOW[now],DFN[next]);
		}
	}
	if(LOW[now]==DFN[now]){//如果now为强连通分量根,输出栈中now及其之后元素,即为一个强连通分量 
		int top_elem;
		do{
			top_elem=Stack.top();
			Stack.pop();
			cout<<top_elem<<' ';
			InSt[top_elem]=false;
		}while(top_elem!=now);
		cout<<endl;
	}
}

int main()
{
	CreateMap();
	memset(DFN,0,sizeof(DFN));
	memset(InSt,false,sizeof(InSt));
	for(int i=1;i<=n;++i)
	    if(!DFN[i]) Tarjan(i);
	
	return 0;
}
/*
6 10
1 4
1 5
1 6
2 1
2 3
5 2
5 3
5 4
6 3
6 5
*/
上一篇:2019.8.17刷题统计


下一篇:约会 Rendezvous:基环树/倍增lca