tarjan求强联通分量

【概述】

Tarjan 算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。

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

【基本思路】

定义 DFN(u) 为节点 u 搜索的次序编号(时间戳),即是第几个被搜索到的,Low(u) 为 u 或 u 的子树能够追溯到的最早的栈中节点的次序号。

每次找到一个新点 i,有:DFN(i)=low(i)

当点 u 与点 v 相连时,如果此时(时间为 DFN[u] 时)v不在栈中,u 的 low 值为两点的 low 值中较小的一个

即:low[u]=min(low[u],low[v])

当点 u 与点 v 相连时,如果此时(时间为 DFN[u] 时)v 在栈中,u 的 low 值为 u 的 low 值和 v 的 dfn 值中较小的一个

即:low[u]=min(low[u],dfn[v]) 

当 DFN(u)=Low(u) 时,以 u 为根的搜索子树上所有节点是一个强连通分量。

【流程】

以下图为例,共有三个强连通分量:1234、5、6

从节点 1 开始 DFS,把遍历到的节点加入栈中,搜索到节点 u=6 时,DFN[6]=LOW[6]=4,找到了一个强连通分量 {6}

返回节点 5,发现 DFN[5]=LOW[5]=3,退栈后 {5} 为一个强连通分量。

返回节点 3,继续搜索到节点 4,把 4 加入堆栈。发现节点 4 像节点 1 的后向边,节点 1 还在栈中,所以 LOW[4]=1。节点 6 已经出栈,不再访问 6,返回 3,(3,4) 为树枝边,所以 LOW[3]=LOW[4]=1。

继续回到节点 1,最后访问节点 2。访问边 (2,4),4 还在栈中,所以 LOW[2]=4。返回 1 后,发现 DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量 {1,3,4,2}。

至此,算法结束。经过该算法,求出了图中全部的三个强连通分量{1,3,4,2}、{5}、{6}。

【时间复杂度】

通过上述流程分析,运行 Tarjan 算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为 O(N+M)。

【实现】(非转载)

#include <stack>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
#define M 10005

vector<int>G[M];

stack<int>ss;

int n, m, u, v, time, ans;
int Low[M], DFN[M], c[M];
//c[i]表示i点所属的强联通系
bool vis[M], flag;

inline void dfs(int x){
    vis[x] = 1;
    int son;
    time++;
    Low[x] = DFN[x] = time;
    ss.push(x);
    int siz = G[x].size();
    for(int i = 0; i < siz; i ++){
        son = G[x][i];
        if( !DFN[son] ){
            dfs(son);
            Low[x] = min(Low[x], Low[son]);
        }
        else if( vis[son] )
            Low[x] = min(Low[x], DFN[son]);
    }
    if( Low[x] == DFN[x] ){
        ans ++;
        while(1){
            int t = ss.top();
            ss.pop();
            c[t] = ans;
            if( t == x )
                break;
        }
    }
}

int main(){
    while( scanf("%d%d", &n, &m) != EOF ){
        if( !n && !m )
            return 0;
        flag = time = ans = 0;
        memset(vis, 0, sizeof(vis));
        memset(DFN, 0, sizeof(DFN));
        while( !ss.empty() )
            ss.pop();
        memset(G, 0, sizeof(G));
        for(int i = 1; i <= m; i ++ ){
            scanf("%d%d", &u, &v);
            G[u].push_back(v);
        }
        for(int i = 1; i <= n; i ++){
            if( !vis[i] )
                dfs(i);
        }
        printf("%d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值