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/ 对,就是给节点画图的那种,感觉好方便

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

原文地址:https://www.cnblogs.com/zhanhonhao/p/11296012.html

时间: 2024-11-05 12:28:29

Tarjan算法分解强连通分量(附详细参考文章)的相关文章

图论算法(6)(更新版) --- Tarjan算法求强连通分量

之前Tarjan算法求强连通分量博文中,代码实现用到了固定大小数组,扩展起来似乎并不是很方便,在java里这样来实现本身就是不太妥当的,所以下面给出一个更新版本的代码实现: package test; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util

Tarjan算法求强连通分量

一.操作过程:tarjan算法的基础是DFS.我们准备两个数组Low和Dfn.Low数组是一个标记数组,记录该点所在的强连通子图所在搜索子树的根节点的 Dfn值(很绕嘴,往下看你就会明白),Dfn数组记录搜索到该点的时间,也就是第几个搜索这个点的.根据以下几条规则,经过搜索遍历该图(无需回溯)和 对栈的操作,我们就可以得到该有向图的强连通分量. 1.数组的初始化:当首次搜索到点p时,Dfn与Low数组的值都为到该点的时间. 2.堆栈:每搜索到一个点,将它压入栈顶. 3.当点p有与点p’相连时,如

转 tarjan算法求强连通分量

无意中想起图的强连通分量来,之前也一直想写所以今天决定来填这个坑.PS:由于本人比较懒,之前做过一个讲解的PPT,不过那是好遥远之前,年代已久早已失传,所以本文里的图来自网络.以后周末都用来填坑也挺好. ------------------------------分割线----------------------------------------- 在有向图G中,如果两个顶点间至少存在一条路径,那么这两个顶点就是强连通(strongly connected). 如果有向图G的每两个顶点都强连通

HDU 1269 迷宫城堡 tarjan算法求强连通分量

基础模板题,应用tarjan算法求有向图的强连通分量,tarjan在此处的实现方法为:使用栈储存已经访问过的点,当访问的点离开dfs的时候,判断这个点的low值是否等于它的出生日期dfn值,如果相等,那这个点就在一个强连通分量里面,此时从栈中向外取出元素,知道取出的元素与这个点的值相等时结束,我们所有取出的点与这个点在同一个强连通分量里.下面是代码,其实代码里本来不需要id数组记录点属于哪个强连通分量的,因为题目没有做要求,但是为了保留模板完整还是带着了,以供以后复习使用. #include<c

图论算法(6) --- Tarjan算法求强连通分量

注:此算法以有向图作为输入,并按照所在的强连通分量给出其顶点集的一个划分.graph中的每个节点只在一个强连通分量里出现,即使是单点. 任选一点开始进行深度优先搜索(若dfs结束后仍有未访问的节点,则再从中任选一点再从进行).搜索过程中已访问的节点不再访问.搜索树的若干子树构成了图的强连通分量. 节点按照被访问的顺序存入栈中.从搜索树的子树返回至一个节点时,检查该节点是否是某一连通分量的根节点,并将其从栈中删除.如果某节点是强连通分量的根,则在它之前出栈且还不属于其他强连通分量的节点构成了该节点

Tarjan算法【强连通分量】

转自:byvoid:有向图强连通分量的Tarjan算法 Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树.搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的所有节点是否为一个强连通分量. 有两个概念:1.时间戳,2.追溯值 时间戳是dfs遍历节点的次序. 定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的栈中节点最小的次序号.由定义可以得出: 1 Low(u)=min{ 2 DFN(u), // 自己的

tarjan算法(强连通分量 + 强连通分量缩点 + 桥 + 割点 + LCA)

这篇文章是从网络上总结各方经验 以及 自己找的一些例题的算法模板,主要是用于自己的日后的模板总结以后防失忆常看看的, 写的也是自己能看懂即可. tarjan算法的功能很强大, 可以用来求解强连通分量,缩点,桥,割点,LCA等,日后写到相应的模板题我就会放上来. 1.强连通分量(分量中是任意两点间都可以互相到达) 按照深度优先遍历的方式遍历这张图. 遍历当前节点所出的所有边.在遍历过程中: ( 1 ) 如果当前边的终点还没有访问过,访问. 回溯回来之后比较当前节点的low值和终点的low值.将较小

Tarjan 算法求 LCA / Tarjan 算法求强连通分量

[时光蒸汽喵带你做专题]最近公共祖先 LCA (Lowest Common Ancestors)_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili tarjan LCA - YouTube Tarjan算法_LCA - A_Bo的博客 - CSDN博客 Tarjan离线算法求最近公共祖先(LCA) - 初学者 - CSDN博客 最近公共祖先(LCA) - riteme.site Fuzhou University OnlineJudge P3379 [模板]最近公共祖先(LCA) - 洛谷 |

【算法】Tarjan算法求强连通分量

概念: 在有向图G中,如果两个定点u可以到达v,并且v也可以到达u,那么我们称这两个定点强连通. 如果有向图G的任意两个顶点都是强连通的,那么我们称G是一个强连通图. 一个有向图中的最大强连通子图,称为强连通分量. tarjan的主要思想: 从一个点开始DFS,记录两个数组,dfn[]和low[]. 其中,dfn[i]指的是到达第i个点的时间. low[i]指第i个点直接或间接可到达的点中的最小dfn[j]. low[i]数组的初始值为dfn[i]. 举个例子,如图所示: 假如我们从第一个点开始