zoukankan      html  css  js  c++  java
  • Tarjan算法分解强连通分量(附详细参考文章)

    Tarjan算法分解强连通分量

    算法思路:

    算法通过dfs遍历整个连通分量,并在遍历过程中给每个点打上两个记号:一个是时间戳,即首次访问到节点i的时刻,另一个是节点u的某一个祖先被访问的最早时刻。

    时间戳用DFN数组存储,最早祖先用low数组来存,每次dfs遍历到一个节点u,即让这两个记号等于当前时刻,在后面回溯或者判断的过程中在来更新low,DNF是一定的,因为第一次访问时刻一定。然后遍历u的子节点,也就是跟u相连的点v,依次看子节点的时间戳有没有打上,也就是看他有没有被访问过。(1).没有就继续dfs点v,在后来回溯的时候,如果v的low比u的low小,也就是v的某一祖先比u还早被访问,因为u和v是直接相连的,说明u的low也该更小,便更新u的low值。(2).如果有,就看这个点有没有在栈里,在的话就看v被访问的时刻是不是比u的low还早,如果是,更新u的low值。

    当遍历完当前节点及其子节点后,检查u的low与DFN是否相等,如果相等,说明已经找到了一个强连通区域,就是现在栈中u及后入栈的节点。挨个pop出来即可处理。

    当这一个tarjan执行完了后不能立马退出,因为图本身有可能是不连通的。还要在循环里tarjan多次。

    以上只是提了下算法的思路,具体的相关概念、讲解、实例和原理都在参考文章里。

    下面是两个版本的tarjan代码,一个是基于邻接矩阵,一个是基于链式前向星(具体介绍参见上一篇博客)。

    代码:

    版本一:邻接矩阵

    #include <iostream>
    #include <memory.h>
    #define max_n 1005
    using namespace std;
    int DFN[max_n];//记录dfs访问次序
    int low[max_n];//记录节点最早可追溯到的祖先
    int cnt = 0;//时间戳
    int Stack[max_n];//模拟栈
    int top = -1;//栈顶
    int flag[max_n];//记录节点是否入栈
    int number = 0;//强连通分量的个数
    int j;//栈弹出节点
    int G[max_n][max_n];//图的邻接矩阵
    int n;//图的节点数目
    void tarjan(int u)
    {
        DFN[u] = low[u] = cnt++;//访问到这个节点打上时间戳
        Stack[++top] = u;//节点入栈
        flag[u] = 1;//节点已在栈内
        for(int i = 0;i<n;i++)
        {
            if(G[u][i])//如果u与i相连
            {
                if(!DFN[i])//如果i节点未被访问过
                {
                    tarjan(u);//继续遍历
                    low[u] = min(low[u],low[i]);//回溯时更新u点的最早祖先值
                }
                else if(flag[i]&&low[u]<DFN[i])//如果i被访问过,并且在栈内
                {
                    low[u] = DFN[i];//更新u点的最早祖先值
                }
            }
            if(DFN[u]==low[u])//如果节点之后全访问完了以后,并且DFN值与最早公共祖先值相等,说明已经得到了一个强连通分量
            {
                number++;//强连通分量个数加一
                do
                {
                    j = Stack[top--];//弹出栈中比该点后入栈的所有点
                    cout << j << " ";
                    flag[j] = 0;//节点不在栈中
                }while(j!=u);//直到把节点u也弹出
                cout << endl;
            }
        }
    }
    
    int main()
    {
        memset(DFN,0,sizeof(DFN));
        memset(flag,0,sizeof(flag));
        memset(Stack,0,sizeof(Stack));
        for(int i = 1;i<=n;i++)//遍历多遍求出所有的强连通分量
        {
            if(DFN[i])
            {
                tarjan(i);
            }
        }
        return 0;
    }
    
    

    版本二:链式前向星

    #include <iostream>
    #include <cstring>
    #include <stack>
    #include <memory.h>
    #define max_n 1005
    using namespace std;
    int n,m;//n为节点个数,m为边的数目
    int idx=0;//时间戳
    int Bcnt=0;//强连通分量的个数
    int instack[max_n];//记录节点是否在栈内
    int dfn[max_n];//dfs访问顺序标号
    int low[max_n];//节点的最早公共祖先
    int Belong[max_n];//存储节点属于哪一个强连通分量
    stack<int> s;//dfs访问时的栈
    //链式前向星结构
    int head[max_n];
    int cnt = 0;
    struct
    {
        int v;
        int next;
    }e[max_n<<1];
    void add(int u,int v)
    {
        ++cnt;
        e[cnt].v = v;
        e[cnt].next = head[u];
        head[u] = cnt;
    }
    //读入数据
    void readdata()
    {
        int a,b;
        memset(head,0,sizeof(head));
        cin >> n >> m;
        for(int i = 0;i<m;i++)
        {
            cin >> a >> b;
            add(a,b);
        }
    }
    
    void tarjan(int u)
    {
        dfn[u] = low[u] = ++idx;
        s.push(u);
        instack[u] = 1;
        int v;
        for(int i = head[u];i;i=e[i].next)//遍历u的相连节点
        {
            v = e[i].v;
            if(!dfn[v])//如果未访问过
            {
                tarjan(v);
                low[u] = min(low[u],low[v]);//尝试更新u的low值
            }
            else if(instack[v])//如果被访问过且在栈中
            {
                low[u] = min(low[u],dfn[v]);//尝试更新u的low值
            }
        }
        if(low[u]==dfn[u])//找到一个联通分量
        {
            Bcnt++;
            do
            {
                v = s.top();
                s.pop();
                instack[v] = 0;
                Belong[v] = Bcnt;
            }while(u!=v);
        }
    }
    //结果处理
    void solve()
    {
        for(int i = 1;i<=n;i++)
        {
            if(!dfn[i])
            {
                tarjan(i);
            }
        }
        for(int i = 1;i<=n;i++)
        {
            cout << "d " << dfn[i] << " l " << low[i] << endl;
        }
        cout << Bcnt << "个强连通分量" << endl;
        for(int i = 1;i<=Bcnt;i++)
        {
            cout << "第 " << i << " 个" << endl;
            for(int j = 1;j<=n;j++)
            {
                if(Belong[j]==i)
                {
                    cout << j << " ";
                }
            }
            cout << endl;
        }
    }
    int main()
    {
        readdata();
        solve();
        return 0;
    }
    
    

    参考文章:

    我大概整理了一下,这些博客会很有帮助

    键盘里的青春,全网最!详!细!tarjan算法讲解,https://blog.csdn.net/qq_34374664/article/details/77488976(主要是原理,实例,我之前看了很多博客云里雾里的,这篇看懂了)

    玩人,Tarjan算法详解,https://blog.csdn.net/jeryjeryjery/article/details/52829142?locationNum=4(算法流程比较清楚,实现基于邻接矩阵,代码有少许错误)

    five20,浅析强连通分量(Tarjan和kosaraju),https://www.cnblogs.com/five20/p/7594239.html(实例再次,代码不错,基于链式前向星)

    BYVoid,有向图强连通分量的Tarjan算法,https://www.byvoid.com/zhs/blog/scc-tarjan (我刚开始看的,由于我本身还不太理解,看不太懂,但渐渐明白后它的讲解还是不错的。偶然发现这个是dalao的网站,dalao网站有繁体字版本(好像鸟哥的),大佬貌似还对中国语言有研究,有很多有意思的文章,本来看算法结果看其他的文章去了(逃)

    最后推荐一个有趣的网站,画图!https://csacademy.com/app/graph_editor/ 对,就是给节点画图的那种,感觉好方便

    觉得不错要不右下角推荐一下?

  • 相关阅读:
    hdu 3006 The Number of set(思维+壮压DP)
    Mysql-SQL优化-统计某种类型的个数
    canvas.clipPath canvas.clipRect() 无效的原因
    linux下alias命令具体解释
    使用带粒子效果的 CAEmitterLayer
    Wordpress 建站(一)
    一个有趣的问题:ls -l显示的内容中total究竟是什么?
    (转)奇妙的数据挖掘
    android几个高速打包命令
    hdu3336解读KMP算法的next数组
  • 原文地址:https://www.cnblogs.com/zhanhonhao/p/11296012.html
Copyright © 2011-2022 走看看