求图的强连通分量--tarjan算法

本文详细介绍Tarjan算法原理及实现步骤,通过DFS访问图中的每个节点并利用dfn和low值来确定强连通分量。文章包含算法核心代码示例,并以一道具体题目为例展示算法的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一:tarjan算法详解

◦思想:
◦做一遍DFS,用dfn[i]表示编号为i的节点在DFS过程中的访问序号(也可以叫做开始时间)用low[i]表示i节点DFS过程中i的下方节点所能到达的开始时间最早的节点的开始时间。(也就是之后的深搜所能到达的最小开始时间)初始时dfn[i]=low[i]
◦在DFS过程中会形成一搜索树。在搜索树上越先遍历到的节点,显然dfn的值就越小。
◦DFS过程中,碰到哪个节点,就将哪个节点入栈。栈中节点只有在其所属的强连通分量已经全部求出时,才会出栈。
◦(对于一个强连通分量和一个指出去的有向边,在输出这个强连通分量的时候,那条指出去的边的终点已经作为一个强连通分量统计,并且出栈了。)
◦如果发现某节点u有边连到搜索树(栈)中栈里的节点v,则更新u的low 值为dfn[v](更新为low[v]也可以,更新为low可以算是压缩路径,速度略快)。
◦如果一个节点u已经DFS访问结束,而且此时其low值等于dfn值,则说明u可达的所有节点,都不能到达任何在u之前被DFS访问的节点 ---- 那么该节点u就是一个强连通分量在DFS搜索树中的根。
◦此时将栈中所有节点弹出,包括u,就找到了一个强连通分量
模板:void Tarjan(u) {/*注意小写的tarjan是关键字,还有index也是*/    dfn[u]=low[u]=++index    stack.push(u)
for each (u, v) in E {
        if (v is not visted) {
            tarjan(v) 
            low[u] = min(low[u], low[v]) 
        }
        else if (v in stack) {
            low[u] = min(low[u], dfn[v]) /*这里是low[u] = min(low[u], low[v])也是可以的,因为如果这个点被访问了,而且仍然待在栈中,说明u---v是一条回边,那么这里用dfn[v]
low[v]都是相同了的*/
}
} if (dfn[u] == low[u])
{ //u是一个强连通分量的根 repeat v = stack.pop
print v
until (u== v) } //退栈,把整个强连通分量都弹出来 } //复杂度是O(E+V)的

 

例题:1001. [WZOI2011 S3] 消息传递(来源sojs.tk)

★★   输入文件:messagew.in   输出文件:messagew.out   简单对比
时间限制:1 s   内存限制:128 MB

#include<iostream>
using namespace std;
#include<cstdio>
#define N 100100
#include<vector>
vector<int>G[N];
vector<int>ans[N];
int clac=0;
bool inzhan[N];
#include<stack>
#include<cstring>
stack<int>s;
int low[N],dfn[N];
bool flag[N]={0},ok[N]={0};
int n,m;
int Index;
void input()
{
    Index=0;
    scanf("%d%d",&n,&m);
    int a,b;
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d",&a,&b);
        G[a].push_back(b);
    }
    memset(inzhan,false,sizeof(inzhan));
    memset(dfn,-1,sizeof(dfn));
    memset(low,-1,sizeof(low));
}
void Tarjan(int k)
{
    int  j;
    dfn[k]=low[k]=++Index;/*tarjan过程,记录到达该点的时间,和从该点出发能到达的时间最小的点*/
    inzhan[k]=true;/*入栈*/
    s.push(k);
    for(int i=0;i<G[k].size();++i)
    {
        int tp=G[k][i];/*邻接表储存,G[k][i]表示从k点出发的第i条边的终点编号*/
        if(low[tp]==-1)
        {
            Tarjan(tp);
            low[k]=min(low[k],low[tp]);
        }
        else if(inzhan[tp])
        low[k]=min(low[tp],low[k]);
        /*整理总共有三种情况:未被访问,访问了但是是不是强连通分量还不知道(也就是仍在栈中),访问了已经出栈了(这种不用处理,因为如果这个点在好几个强连通分量中,那么他此时一定没有出栈。因为深搜嘛)*/
    }
    if(low[k]==dfn[k])/*low[k]==dfn[k]是这个强连通分量的标志,也就是起始位置,不能再向上找了*/
    {
        int l;
        clac++;/*强连通分量个数*/
        do{
        l=s.top();
        s.pop();
        ans[clac].push_back(l);
        inzhan[l]=false;
        }while(l!=k);
       if(ans[clac].size()>1)
       flag[clac]=true;/*记录这个强连通分量中的所有点是可以传话成功的,下面在重标记每一个点*/
    }
}
void OUT()
{
    for(int i=1;i<=clac;++i)
    if(flag[i])
    {
        for(int j=0;j<ans[i].size();++j)
        ok[ans[i][j]]=true;
    }
    for(int i=1;i<=n;++i)
    if(ok[i])
    printf("T\n");
    else printf("F\n");
}
int main()
{
    freopen("messagew.in","r",stdin);
    freopen("messagew.out","w",stdout);
    input();
    for(int i=1;i<=n;++i)
    if(dfn[i]==-1)
    Tarjan(i);
    OUT();
    fclose(stdin);
    fclose(stdout);
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/c1299401227/p/5402414.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值