• Tarjan学习笔记


    引言

    有向图$G$中,如果两个节点能够相互到达,那么这就是一个连通分量,如果每两个节点都连通,那么这就是一个强联通图。

    有向图的极大强连通子图成为一个强连通分量,而$Tarjan$算法便是用来求强连通分量的算法。

    算法介绍

    $Tarjan$算法是由$Robert Tarjan$提出的,该算法时间复杂度为$O(N+M)$,属于线性复杂度。

    再此膜拜神仙   $\%\%\% STO Tarjan ORZ \%\%\%$

    $Tarjan$算法是用$DFS$实现的算法,在一棵搜索树当中,每一个强连通分量都是一个子树

    定义$DFN(U)$表示节点$U$被访问到的次序编号,$LOW(U)$表示节点$U$或者$U$的子树在退栈时所能遍历到的最小的次序编号

    显而易见,当$DFN(U) == LOW(U)$时,以$U$节点为$root$的子树就是一个强联通分量。

    我们在遍历的时候,如果先将当前的节点压入栈中,由$vis$数组对其进行标记,表示已经入过栈然后遍历这个节点的所有边

    如果边的终点的$DFN$已经有值了,就代表$DFN$已经访问过了,那么就不需要对其进行遍历,只需要确定它与当前节点的关系

    否则就对其进行遍历,在回溯的时候将其$LOW$值与经过的点进行比较取最小值

    下面是$Tarjan$算法的伪代码,来自BYvoid大神的blog,如果你想进去,请先挂上梯子

    tarjan(u)
    {
        DFN[u]=Low[u]=++Index                      // 为节点u设定次序编号和Low初值
        Stack.push(u)                              // 将节点u压入栈中
        for each (u, v) in E                       // 枚举每一条边
            if (v is not visted)               // 如果节点v未被访问过
                tarjan(v)                  // 继续向下找
                Low[u] = min(Low[u], Low[v])
            else if (v in S)                   // 如果节点v还在栈内
                Low[u] = min(Low[u], DFN[v])
        if (DFN[u] == Low[u])                      // 如果节点u是强连通分量的根
            repeat
                v = S.pop                  // 将v退栈,为该强连通分量中一个顶点
                print v
            until (u== v)
    }

    流程演示

    接下来的图片也是来自BYvoid的blog

    注:左边的绿色的框框代表堆栈,最底下是栈顶哦^(* ̄(oo) ̄)^

    step1

    先从节点$1$开始遍历,一直遍历到节点$6$,$LOW(6) == DFN(6)$,说明节点$6$就是一个强连通分量

    step2

    回溯到节点$5$之后,发现$LOW(5) == DFN(5)$,同理,节点$5$也是一个强连通分量

    step3

    继续回溯到节点$3$,发现节点$3$还有边可以走,遍历到节点$4$,用从节点$4$遍历到$1$,这时发现$1$已经在栈里了。节点$4$的$LOW$值就变成了$1$

    step4

    继续回溯,回溯到节点$1$之后,又遍历到节点$2$,又从节点$2$遍历到节点$4$,这时发现$4$已经在栈里了,同理,节点$2$的$LOW$值将变成$5$,因为是将节点$4$的$DFN$值和节点$2$的$LOW$值取最小值

    然后回溯到节点$1$,发现节点$1$的$LOW$值和$DFN$值相等,就开始退栈,一直到栈顶元素不等于$1$

    至此,该算法完成,这张图一共有三个强连通分量,分别是

    1 2 3 4
    5
    6
    

      

    复杂度分析

    至于$Tarjan$算法的复杂度,因为每个节点只入过一次栈,并且每条边只访问过一次,所以它的时间复杂度是线性的,为$O(M+N)$

    吐槽一句,这个算法的名字的读法,很奇怪,我出去学习的时候老师说应该读tǎ yáng,但是我的学长们说应该对tǎ jiān,不过,我比较喜欢叫它tài jiān,这多接地气。。。。QwQ

    Tarjan代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <stack>
    #include <cmath>
    #define MAXN 100050
    #define INF  2147483647
    
    using namespace std;
    
    int n, m, tot;
    int u[MAXN], v[MAXN], w[MAXN];
    int first[MAXN], next[MAXN];
    int dfn[MAXN], low[MAXN];
    bool vis[MAXN];
    stack<int> S;
    
    void Tarjan(int x) {
        dfn[x] = low[x] = ++tot;
        S.push(x);
        vis[x] = 1;
        int k = first[x];
        while(k != -1) {
            if(!dfn[v[k]]) {
                Tarjan(v[k]);
                low[x] = min(low[x], low[v[k]]);
            }
            else if(vis[v[k]] == 1&&dfn[v[k]]) {
                low[x] = min(low[x], low[v[k]]);
            }
            k = next[k];
        }
        if(dfn[x] == low[x]) {
            while(!S.empty()) {
                int temp = S.top();
                S.pop();
                printf("%d ", temp);
                vis[temp] = 0;
                if(temp == x) break;
            }
            printf("
    ");
        }
        return ;
    }
    
    int main() {
        scanf("%d%d", &n, &m);
        memset(first, -1, sizeof(first));
        for(int i=1; i<=m; i++) {
            scanf("%d%d", &u[i], &v[i]);
            w[i] = 1;
            next[i] = first[u[i]];
            first[u[i]] = i;
        }
        for(int i=1; i<=n; i++)
            if(!dfn[i])
                Tarjan(i);
        return 0;
    }

      

    附赠大家一组样例

    Sample Input

    6 8
    1 3
    1 2
    2 4
    3 4
    3 5
    4 6
    4 1
    5 6
    

      

    Sample Output

    6
    5
    3 4 2 1
    

      

  • 相关阅读:
    Web网页配色方案及安全色谱
    JQuery获取元素文档大小、偏移和位置和滚动条位置的方法集合
    IE8的css hack
    jquery 获取元素坐标
    CSS 颜色值
    正则表达式
    JS获取宽度
    常用正则表达式例子
    HTML转义字符
    myeclipse 10.0 含破解补丁/注册机
  • 原文地址:https://www.cnblogs.com/bljfy/p/9299735.html
Copyright © 2020-2023  润新知