『Tarjan算法 有向图的强连通分量』


有向图的强连通分量

定义:在有向图\(G\)中,如果两个顶点\(v_i,v_j\)间\((v_i>v_j)\)有一条从\(v_i\)到\(v_j\)的有向路径,同时还有一条从\(v_j\)到\(v_i\)的有向路径,则称两个顶点强连通(strongly connected)。如果有向图\(G\)的每两个顶点都强连通,称\(G\)是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

万能的Tarjan算法也可以帮助我们求解有向图的强联通分量。

预备知识

时间戳

图在深度优先遍历的过程中,按照每一个节点第一次被访问到的顺序给\(N\)个节点\(1-N\)的标记,称为时间戳,记为\(dfn_x\)。

追溯值

设节点\(x\)可以通过搜索树以外的边回到祖先,那么它能回到祖先的最小时间戳称为节点\(x\)的追溯值,记为\(low_x\)。当\(x\)没有除搜索树以外的边时,\(low_x=x\)。

Tarjan 算法

著名的\(Tarjan\)算法可以在线性时间内求解有向图的强联通分量。

  • 举个栗子,右图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。

\(Tarjan\)求强连通分量的过程仍然是在递归求解\(dfn\),\(low\)的过程中利用这两个数组实现的(如何求解可以参见『Tarjan算法 无向图的割点与割边』),其原理如下。

\(Tarjan\)算法将每个强连通分量看作图的搜索树中的一棵子树,搜索时,将每一个未回溯的节点加入一个栈,回溯时若\(dfn\)值与\(low\)值相等,则得到栈顶的若干节点即为一个强连通分量。

我的理解:
回溯时若\(dfn\)值与\(low\)值相等,则说明以当前节点为根的子树中的若干节点都通过直接或间接路径返回到了当前节点,而当前节点到那些节点显然是可行的。也就是说,它们形成了若干个环,构成了一个强连通分量。

实际上,\(low\)数组就是不断在找"环"结构的过程。

其流程如下:
对于每一个当前访问的点:

1.更新\(dfn\)和\(low\)的初始标记,\(low=dfn\)
2.遍历当前节点的每一个子节点
3.如果其子节点未标记\(dfn\)值,访问并更新,并顺带更新\(low\)值
4.如果已经访问标记了\(dfn\)值,并且其子节点还在栈中,则该边是一条返祖边,更新\(low\)值
5.完成所有子节点的遍历后,判断\(dfn\)是否等于\(low\),若相等,则说明当前栈顶的若干点(直到栈顶节点为当前节点)构成了一个强连通分量,记录即可

\(Code:\)

inline void Tarjan(int x)
{
    dfn[x]=low[x]=++cnt;
    Stack.push(x);inSta[x]=true;
    for(int i=Last[x];i;i=e[i].next)
    {
        int y=e[i].ver;
        if(!dfn[y])
        {
            Tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(inSta[y])low[x]=min(low[x],dfn[y]);
    }
    if(dfn[x]==low[x])
    {
        int top=0;tot++;
        while(top!=x)
        {
            top=Stack.top();
            Stack.pop();
            inSta[top]=false;
            con[top]=tot;
            size[tot]++;
            //这些点都在编号为tot的一个强连通分量中,con为查询强连通分量的数组,size为强连通分量的大小
            //储存方式需要适时改变,以应和题目
        }
    }
}

Tarjan算法的应用

通常,我们可以通过\(tarjan\)算法找到有向图中的强连通分量,若将各个强连通分量压缩成一个点,我们就得到了一个有向无环图(\(DAG\)),这对我们的解题过程可以有所帮助。

最受欢迎的牛

Description

每头牛都有一个梦想:成为一个群体中最受欢迎的名牛!

在一个有N(1<=N<=10,000)头牛的牛群中,给你M(1<=M<=50,000)个二元组(A,B),表示A认为B是受欢迎的。

既然受欢迎是可传递的,那么如果A认为B受欢迎,B又认为C受欢迎,则A也会认为C是受欢迎的,哪怕这不是十分明确的规定。你的任务是计算被所有其它的牛都喜欢的牛的个数。

Input Format

第一行,两个数,N和M。

第2~M+1行,每行两个数,A和B,表示A认为B是受欢迎的。

Output Format

一个数,被其他所有奶牛认为受欢迎的奶牛头数。

Sample Input

3 3
1 2
2 1
2 3

Sample Output

1

解析

将牛的欢迎关系视为图的连边后,我们就得到了一张有向图,不过不能保证无环。

我们放宽限制,假设给出的是有向无环图,可以尝试几组样例。
发现规律后我们可以得到猜想:若有且仅有一个点出度为0,则该点符合要求,答案总数为1,若有多于一个点出度为0,则没有符合要求的点,答案总数为0。

那么对于原图,我们把每一个强连通分量压缩为一个点,按有向无环图的规律得到答案即可。若符合要求的点是一个由强连通分量压缩得到的点,则答案数量为该强连通分量的大小。

这就成了一道强连通分量缩点模板题。

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
const int N=20000+200,M=80000+200;
int n,m,dfn[N],low[N],cnt,Last[M*2],t,con[N],tot,inSta[N],outdeg[N],size[N],ans=0;
stack < int > Stack;
struct edge{int ver,next;}e[M*2];
inline void insert(int x,int y)
{
    e[++t].ver=y;e[t].next=Last[x];Last[x]=t;
}
inline void input(void)
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        insert(x,y);
    }
}
inline void Tarjan(int x)
{
    dfn[x]=low[x]=++cnt;
    Stack.push(x);inSta[x]=true;
    for(int i=Last[x];i;i=e[i].next)
    {
        int y=e[i].ver;
        if(!dfn[y])
        {
            Tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(inSta[y])low[x]=min(low[x],dfn[y]);
    }
    if(dfn[x]==low[x])
    {
        int top=0;tot++;
        while(top!=x)
        {
            top=Stack.top();
            Stack.pop();
            inSta[top]=false;
            con[top]=tot;
            size[tot]++;
        }
    }
}
inline void build(void)
{
    for(int i=1;i<=n;i++)
        for(int j=Last[i];j;j=e[j].next)
            if(con[i]^con[e[j].ver])outdeg[con[i]]++;
}
inline void find(void)
{
    int flag=0;
    for(int i=1;i<=tot;i++)
        if(!outdeg[i])
        {
            if(!flag)flag=i;
            else
            {
                ans=0;
                return;
            }
        }
    ans=size[flag];
}
int main(void)
{
    input();
    for(int i=1;i<=n;i++)
        if(!dfn[i])Tarjan(i);
    build();
    find();
    printf("%d\n",ans);
    return 0;
}


对于\(Tarjan\)求强连通分量的理解,还可以参照这篇博客

原文地址:https://www.cnblogs.com/Parsnip/p/10389130.html

时间: 2024-08-25 19:47:17

『Tarjan算法 有向图的强连通分量』的相关文章

poj2186Popular Cows(Kosaraju算法--有向图的强连通分量的分解)

1 /* 2 题目大意:有N个cows, M个关系 3 a->b 表示 a认为b popular:如果还有b->c, 那么就会有a->c 4 问最终有多少个cows被其他所有cows认为是popular! 5 6 思路:强连通分量中每两个节点都是可达的! 通过分解得到最后一个连通分量A, 7 如果将所有的强连通分量看成一个大的节点,那么A一定是孩子节点(因为我们先 8 完成的是父亲节点的强连通分量)! 最后如果其他的强连通分量都可以指向A,那么 9 A中的每一个cow都会被其他cows所

有向图的强连通分量(tarjan算法)

强连通分量 有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.有向图的极大强连通子图,称为强连通分量(strongly connected components). 考虑强连通分量C,设其中第一个被发现的点为x,则,C中其他的点都是x的后代.我们希望在x访问完成时立即输出C(可以同时记录C,输出代表

hdu1269 迷宫城堡,有向图的强连通分量 , Tarjan算法

hdu1269 迷宫城堡 验证给出的有向图是不是强连通图... Tarjan算法板子题 Tarjan算法的基础是DFS,对于每个节点.每条边都搜索一次,时间复杂度为O(V+E). 算法步骤: 1.搜索到某一个点时,将该点的Low值标上时间戳,然后将自己作为所在强连通分量的根节点(就是赋值Dfn=Low=time) 2.将该点压入栈. 3.当点p有与点p'相连时,如果此时p'不在栈中,p的low值为两点的low值中较小的一个. 4.当点p有与点p'相连时,如果此时p'在栈中,p的low值为p的lo

『Tarjan算法 无向图的割点与割边』

无向图的割点与割边 定义:给定无相连通图\(G=(V,E)\) 若对于\(x \in V\),从图中删去节点\(x\)以及所有与\(x\)关联的边后,\(G\)分裂为两个或以上不连通的子图,则称\(x\)为\(G\)的割点. 若对于\(e \in E\),从图中删去边\(e\)之后,\(G\)分裂为两个不连通的子图,则称\(e\)为\(G\)的割边. 对于很多图上问题来说,这两个概念是很重要的.我们将探究如何求解无向图的割点与割边. 预备知识 时间戳 图在深度优先遍历的过程中,按照每一个节点第一

求有向图的强连通分量的算法

下面是求有向图的强连通分量的算法的代码: import java.util.Scanner; class Qiufenliang//定义求强连通分量的类 { String lu="";//定义的一个字符型变量,记录强连通分量的路径 public static int s=0; public void qiu(int a[][],int l)//定义函数,参数a为二维数组,参数l为数组的维数 { int t=0;//定义int型变量,进行数量的统计 for(int i=1;i<l;

【原创】tarjan算法初步(强连通子图缩点)

tarjan算法的思路不是一般的绕!!(不过既然是求强连通子图这样的回路也就可以稍微原谅了..) 但是研究tarjan之前总得知道强连通分量是什么吧.. 上百度查查: 有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.有向图的极大强连通子图,称为强连通分量(strongly connected co

Tarjan 算法求强联通分量

转载自:http://blog.csdn.net/xinghongduo/article/details/6195337 还是没懂Tarjan算法的原理.但是感觉.讲的很有道理. 说到以Tarjan命名的算法,我们经常提到的有3个,其中就包括本文所介绍的求强连通分量的Tarjan算法.而提出此算法的普林斯顿大学的Robert E Tarjan教授也是1986年的图灵奖获得者. 首先明确几个概念. 强连通图.在一个强连通图中,任意两个点都通过一定路径互相连通.比如图一是一个强连通图,而图二不是.因

UVA247- Calling Circles(有向图的强连通分量)

题目链接 题意: 给定一张有向图,找出所有强连通分量,并输出. 思路:有向图的强连通分量用Tarjan算法,然后用map映射,便于输出,注意输出格式. 代码: #include <iostream> #include <cstdio> #include <cstring> #include <map> #include <algorithm> using namespace std; const int MAXN = 2000; const in

图-&gt;连通性-&gt;有向图的强连通分量

文字描述 有向图强连通分量的定义:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.有向图的极大强连通子图,称为强连通分量(strongly connected components). 用深度优先搜索求有向图的强连通分量的方法如下并假设有向图的存储结构为十字链表. 1 在有向图G上,从某个定点出发沿以该顶点为尾的弧