C++学习笔记:有向图的强连通分量

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_44013342/article/details/90714577

强连通图分量

首先得知道这是个什么玩意儿,对于一个如下的有向图

在这个有向图G中,如果有两个点可以相互到达,则两点为强连通,若图中每个点都可以相互到达,则图G为强连通图

1. 一个有向图是强连通的,而且仅当G中有一条回路,它至少包含每个点一次

2. 非强连通图的极大强连通子图,称为强连通分量,孤立的点也是一个强连通分量

(copy课件不解释)

 


实现方法

1. Kosaraju算法

(1)首先DFS遍历一遍图,在DFS回溯中标记点,得到一个后序遍历的表

(2)一个原图G的反图R,按 1 中的表DFS遍历

(3)在 2 中,用了多少次DFS,就有多少个强连通分量

为什么可以这样实现,可以理解为如果点 i 和点 j 强连通,则 i 可以到 j,j 可以到 i,则在反图中也可以相互到达

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#include <vector>
using namespace std;

int m , n , ans ;//n个点,m条边
bool vis[10005] ;//是否访问
vector<int> G[10005] , R[10005] ;
stack<int> S ;//表

void DFS_G( int x ) {//原图遍历
	vis[x] = 1 ;
	for(int i = 0 ; i < G[x].size() ; ++ i ) {
		int s = G[x][i] ;
		if( !vis[s] ) 
			DFS_G( s );
	}
	S.push( x ) ;//放入表中
	return ;
}

void DFS_R( int x ) {//反图遍历
	vis[x] = 1 ;
	for(int i = 0 ; i < R[x].size() ; ++ i ) {
		int s = R[x][i] ;
		if( !vis[s] )
			DFS_R( s ) ;
	}
	return ;
}

int main() {
	while( scanf("%d%d", &n , &m ) != EOF && ( m || n ) ) {
		memset( vis , 0 , sizeof(vis) );
		ans = 0 ;
		while( !S.empty() ) //清空表
			S.pop() ; 
		for(int i = 1 ; i <= m ; ++ i ) {
			int a , b ;
			scanf("%d%d", &a , &b );
			G[a].push_back(b) ;
			R[b].push_back(a) ;  
		}
		for(int i = 1 ; i <= n ; ++ i ) {
			if( !vis[i] )
				DFS_G( i );
		}
		memset( vis , 0 , sizeof(vis) );
		while( !S.empty() ) {//搜索反图
			int x = S.top() ;
			S.pop();
			if( !vis[x] ) {
				DFS_R( x ) ;
				ans ++ ;
			}
		}
		printf("%d\n", ans );
		for(int i = 1 ; i <= n ; ++ i )
			G[i].clear() , R[i].clear() ;
	}
}

2. Tarjan算法

Tarjan算法是基于DFS的,每个强连通分量为搜索树的一棵子树

搜索时把当前搜索树中未处理的节点加入一个栈,回溯时判断栈顶到栈中节点是否为强连通分量

(极其难理解,点进来详解

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <stack>
using namespace std;

int n , m ;// n 个点,m 条边 
int cnt , K[1005] , num ;// cnt强连通分量 ,K表示每个节点所属的强连通分量  
int DFN[1005] , low[1005] ;
bool inS[1005] ;//vis表示是否访问,inS表示是否在栈中 
vector<int> G[1005] ;
stack<int> S ;
int from[2005] , to[2005] ;//存边 
bool L[1005] ;//是否有入度 

void init() {
	memset( inS , 0 , sizeof(inS) );
	memset( K , 0 , sizeof(K) );
	memset( DFN , 0 , sizeof(DFN) );
	memset( low , 0 , sizeof(low) );
	memset( from , 0 , sizeof(from) );
	memset( to , 0 , sizeof(to) );
	memset( L , 0 , sizeof(L) );
	cnt = 0 ;
	num = 0 ;
	while( !S.empty() )
		S.pop() ;
}

void Tarjan( int x ) {
	num ++ ;
	DFN[x] = low[x] = num ;//num表示在栈中的编号
	inS[x] = 1 ;
	S.push( x ) ; 
	for( int i = 0 ; i < G[x].size() ; ++ i ) {//搜索相连节点 
		int s = G[x][i] ;
		if( !DFN[s] ) {//没搜索过 
			Tarjan( s );
			low[x] = min( low[x] , low[s] );//更新所能到的上层节点 
		}
		else if( inS[s] ) {//在队中 
			low[x] = min( low[x] , DFN[s] );//到栈中最上端的节点 
		}
	}
	if( low[x] == DFN[x] ) {
		cnt ++ ;
		int y ;
		do{//用 do_while 避免自己没被处理 
			y = S.top() ;
			inS[y] = 0 ;
			S.pop() ;
			K[y] = cnt ;
		}while( y != x );
	}
	return ;
}

int main() {
	while( scanf("%d%d", &n, &m ) != EOF ) {
		init();//初始化 
		for(int i = 1 ; i <= m ; ++ i ) {//输入 
			int a , b ;
			scanf("%d%d", &a , &b );
			G[a].push_back(b); 
			from[i] = a , to[i] = b ;
		}
		for(int i = 1 ; i <= n ; ++ i ) {//找强连通分量 
			num = 0 ;
			if( !DFN[i] )
				Tarjan( i );
		}
		printf("%d\n", cnt );
		for(int i = 1 ; i <= n ; ++ i )
			G[i].clear() ;
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44013342/article/details/90714577