强连通分量 Tarjan算法

什么是Tarjan算法!!!!
Tarjan算法:一种由Robert Tarjan提出的求解有向图强连通分量的线性时间的算法。

这时候你可能会问什么是强连通,什么是强连通分量?

根据百度百科可知:
如果两个顶点可以相互通达,则称两个顶点强连通(Strongly Connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(Strongly Connected Components)。

那么Tarjan算法的作用就是来求强连通分量了

首先,又是这个满网飞的图!
这里写图片描述

上图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。

Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。
当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。

接下来对这个算法进行演示:

这里写图片描述
①从节点1开始进行dfs,将可以遍历到的点都加到栈中。搜索到节点u = 6时, 发现节点6不能再继续向下遍历,这时开始进行回溯,发现有DFN[6] = Low[6] = 4,这时候我们便可以认为找到了一个连通分量{6}。
这里写图片描述
②返回节点5,发现DFN[5] = Low[5] = 3,退栈后又得出一个强连通分量{5}。
这里写图片描述
③返回节点3,发现节点3还有另外的路径可以走,所以继续搜索到节点4,把4加入到栈中。再从节点4开始搜索找到节点6发现DFN[6]已经有值,就不管他,又发现节点1——他父亲的父亲,说明节点1在这个强连通分量中,并且Low[4] = min(Low[4], DFN[1]),所以确定关系,在这棵强连通子树中,节点4要比节点1出现的晚,所以节点4是节点1的子节点,所以Low[4] = 1,再从节点4回到节点3,判断Low[3] = min(Low[3], Low[4]) = 1(Ps:更新该节点所能找到最早的节点是谁),由节点3继续回到节点1,判断判断 Low[1] = min(Low[1], Low[3]) = 1
这里写图片描述
④这时候发现节点1仍然还有没走过的边,这时便又很愉快地走向了节点2,DFN[2]=Low[2]=6,发现节点2还能走向节点4,并且节点4还在栈中,Low[2] = min(Low[2], DFN[4]) Low[2]=4,说明2是4的一个子节点,再由节点2回溯到节点1,Low[1] = min(Low[1], Low[2]) = Low[1] = 1。判断DFN[1] = Low[1],将栈中1以及1之后进栈的所有点都出栈。

⑤然而并没有完,万一你只走了一遍tarjan整个图没有找完怎么办呢?!
所以。Tarjan的调用最好在循环里解决。例如,如果这个点没有被访问过,那么就从这个点开始tarjan一遍。因为这样好让每个点都被访问到。

下面贴一道裸题的代码

Input:

6 8

1 3

1 2

2 4

3 4

3 5

4 6

4 1

5 6

Output:

6

5

3 4 2 1

#include <cstdio>
#include <algorithm>
#include <iterator>
#include <cmath>

using namespace std;
#define M(x) memset(x, -1, sizeof(x))

const int maxn = 1e5 + 5;
const int maxm = 5e3 + 5;
struct Data{ int to, next; }edges[maxm];
int N, M, cnt, tot, index;
int A[maxn], DFN[maxn], Low[maxn], first[maxn], stack[maxn], vis[maxn];
//vector <int> vec;

inline int addedge(int u, int v){
    edges[++cnt].to = v;
    edges[cnt].next = first[u];
    first[u] = cnt;
}

void Tarjan(int u){
    DFN[u] = Low[u] = ++tot;
    stack[++index] = u;
    vis[u] = 1;
    for(int i = first[u]; i; i = edges[i].next){
        int v = edges[i].to;
        if(!DFN[v]){
            Tarjan(v);
            Low[u] = min(Low[u], Low[v]);
        } 
        else if(vis[v]) Low[u] = min(Low[u], DFN[v]);
    }
    if(DFN[u] == Low[u]){
        do{
//          Print();
            printf("%d ",stack[index]);
            vis[stack[index]] = 0;
            index--;
        }while(u != stack[index + 1]);
        printf("\n");
    }
}

inline int GetInt(){
    char x;
    int ret;
    while((x = getchar()) < '0' || x > '9');
    ret = x - '0';
    while((x = getchar()) >= '0' && x <= '9') ret = ret * 10 + x - '0';
    return ret;
}

int main(){
    N = GetInt(), M = GetInt();
    for(int i = 1, u, v; i <= M; i++){
        u = GetInt(), v = GetInt();
        addedge(u, v);
    }
    for(int i = 1; i <= N; i++) if(!DFN[i]) Tarjan(i);
    return 0;
} 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值