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

it2023-09-13  75

强连通图分量

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

在这个有向图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;

}  

 

 

最新回复(0)