tarjan算法

本文只给最直接的结论而没有证明,想看详细教程的可以退出了!

预备知识:
有向图:图中任意两个节点的连边是有方向的。
强连通:两个节点u,v。如果能在图中找出一条路劲从u到v,也能找出一条从v到u的路径,则称这两个节点是强连通的。
强连通图:图中任意两个节点之间都是强连通的。
极大强连通子图:往这个子图中加任意一个点都会使之不是强连通图,那么这个子图就是极大连通子图,也称强连通分量

在这里插入图片描述
改图的强连通分量有(1,2,3,4), (6), (5)。

tarjan算法旨在找出有向图的强连通分量。
我们确立两个数组
dfn[u]:利用深度优先搜索确定的u的dfs序
low[u]: u及u的子孙节点能够到的dfn最小的且未被删除的dfn序(未被删除可以暂时忽略),初始时我们将每个节点的low值设为dfn值(每个节点至少可以够到自己)

一些结论(不给证明)
一个强连通分量有且仅有一个唯一的根
如果节点u,有dfn[u] == low[u],则u是强连通分量的一个根
我们可以任意找一个节点进行dfs,将未访问的点推入一个栈s中,当这跳入没有了出度,正在回溯时,我们可以回溯点的dfn与low是否相等,如果相等,我们可以将该节点在栈中以上的点全部推出,则该节点与退出的所有节点构成一个强连通分量

void tarjan(int u)
{
    dfn[u] = low[u] = ++d_time;
    s.push(u), instack[u] = 1;//遇到一个点就放进栈中
    for(int i = 0; i < (int) g[u].size(); ++i)
    {
        int v = g[u][i];
        if(!dfn[v])//v还未被访问
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);//回溯时更新low值
        }
        else if(instack[v])//v未被访问但是仍然在栈中
        {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if(dfn[u] == low[u])
    {
        k++;
        while(true)
        {
            int temp = s.top();
            s.pop();
            instack[temp] = 0;
            if(temp == u) break;
        }
    }
}

模板

题解:

#include<bits/stdc++.h>

using namespace std;

const int N = 1e4 + 2;

int n,m, dfn[N], low[N], instack[N], d_time;
int k;//强连通分量的个数
vector<int> g[N];
stack<int> s;

void tarjan(int u)
{
    dfn[u] = low[u] = ++d_time;
    s.push(u), instack[u] = 1;//遇到一个点就放进栈中
    for(int i = 0; i < (int) g[u].size(); ++i)
    {
        int v = g[u][i];
        if(!dfn[v])//v还未被访问
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);//回溯时更新low值
        }
        else if(instack[v])//v未被访问但是仍然在栈中
        {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if(dfn[u] == low[u])
    {
        k++;
        while(true)
        {
            int temp = s.top();
            s.pop();
            instack[temp] = 0;
            if(temp == u) break;
        }
    }
}

void init()
{
    while(!s.empty()) s.pop();
    d_time = k = 0;
    for(int i = 1; i <= n; ++i)
    {
        g[i].clear();
    }
    memset(instack + 1, 0, n*sizeof(int));
    memset(dfn+1, 0, n*sizeof(int));
    memset(low + 1, 0, n * sizeof(int));
}

int main()
{
    while(scanf("%d%d", &n, &m) && (n || m))
    {
        init();
        for(int i = 0; i < m; ++i)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u].push_back(v);
        }
        for(int i = 1; i <= n; ++i)
        {
            if(!dfn[i])//该图可能是不连通的,我们需要尝试遍历每一个点
            {
                tarjan(i);
            }
        }
        if(k == 1)
        {
            printf("Yes\n");
        }
        else
        {
            printf("No\n");
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值