版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
强连通图分量
首先得知道这是个什么玩意儿,对于一个如下的有向图
在这个有向图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;
}