强联通分量算法的个人详解Tarjan算法(包含缩点)

13人阅读 评论(0) 收藏 举报
分类:

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

以下是引用大佬的理解:........................

然后我们理解定义:

既然我们现在已经了解了什么是强连通,和什么是强连通分量,可能大家对于定义还是理解的不透彻,我们不妨引入一个图加强大家对强连通分量和强连通的理解:

标注棕色线条框框的三个部分就分别是一个强连通分量,也就是说,这个图中的强连通分量有3个。

其中我们分析最左边三个点的这部分:

其中1能够到达0,0也能够通过经过2的路径到达1.1和0就是强连通的。

其中1能够通过0到达2,2也能够到达1,那么1和2就是强连通的。

...

...

...

同理,我们能够看得出来这一部分确实是强连通分量,也就是说,强连通分量里边的任意两个点,都是互相可达的。


那么如何求强连通分量的个数呢?另外强连通算法能够实现什么一些基本操作呢?我们继续详解、


接着我们开始接触算法,讨论如何用Tarjan算法求强连通分量个数:

Tarjan算法,是一个基于Dfs的算法(如果大家还不知道什么是Dfs,自行百度学习),假设我们要先从0号节点开始Dfs,我们发现一次Dfs我萌就能遍历整个图(树),而且我们发现,在Dfs的过程中,我们深搜到 了其他强连通分量中,那么俺们Dfs之后如何判断他喵的哪个和那些节点属于一个强连通分量呢?我们首先引入两个数组:

①dfn【】

②low【】

第一个数组dfn我们用来标记当前节点在深搜过程中是第几个遍历到的点。第二个数组是整个算法核心数组,我们稍后再说,这个时候我们不妨在纸上画一画写一写,搞出随意一个Dfs出来的dfn数组来观察一下(假设我们从节点0开始的Dfs,其中一种可能的结果是这样滴):



这个时候我们回头来看第二个数组要怎样操作,我们定义low【u】=min(low【u】,low【v】(即使v搜过了也要进行这步操作,但是v一定要在栈内才行)),u代表当前节点,v代表其能到达的节点。这个数组在刚刚到达节点u的时候初始化:low【u】=dfn【u】。然后在进行下一层深搜之后回溯回来的时候,维护low【u】。如果我们发现了某个节点回溯之后的low【u】值还是==dfn【u】的值,那么这个节点无疑就是一个关键节点:从这个节点能够到达其强连通分量中的其他节点,但是没有其他属于这个强连通分量以外的点能够到达这个点,所以这个点的low【u】值维护完了之后还是和dfn【u】的值一样,口述可能理解还是相对费劲一些,我们走一遍流程图:


①首先进入0号节点,初始化其low【0】=dfn【0】=1,然后深搜到节点2,初始化其:low【2】=dfn【2】=2,然后深搜到节点1,初始化其:low【1】=dfn【1】=3;

②然后从节点1开始继续深搜,发现0号节点已经搜过了,没有继续能够搜的点了,开始回溯维护其值。low【1】=min(low【1】,low【0】)=1;low【2】=min(low【2】,low【1】)=1;low【0】=min(low【0】,low【2】)=1;

这个时候猛然发现,low【0】==dfn【0】,这个时候不要太开心,就断定一定0号节点是一个关键点,别忘了,这个时候还有3号节点没有遍历,我们只有在其能够到达的节点全部判断完之后,才能够下结论,所以我们继续Dfs。

④继续深搜到3号节点,初始化其low【3】=dfn【3】=4,然后深搜到4号节点,初始化其:low【4】=dfn【4】=5,这个时候发现深搜到底,回溯,因为节点4没有能够到达的点,所以low【4】也就没有幸进行维护即:low【4】=dfn【4】(这个点一定是强连通分量的关键点,但是我们先忽略这个点,这个点没有代表性,一会分析关键点的问题),然后回溯到3号节点,low【3】=min(low【3】,low【4】)=4;发现low【3】==dfn【3】那么这个点也是个关键点,我们同样忽略掉。

⑤最终回溯到节点0,进行最后一次值的维护:low【0】=min(low【0】,low【3】)=0,这个时候我们猛然发现其dfn【0】==low【0】,根据刚才所述,那么这个点就是一个关键点:能够遍历其属强连通分量的点的起始点,而且没有其他点属于其他强连通分量能够有一条有向路径连到这个节点来的节点

※※大家仔细理解一下这句话,因为这个点属于一个强连通分量,而且强连通分量中的任意两个节点都是互达的,也就是说强连通分量中一定存在环,这个最后能够回到0号节点的1号节点一定有机会维护low【1】,因为0号节点是先进来的,所以其low【1】的值也一定会跟着变小,然后在回溯的过程中,其属一个强连通分量的所有点都会将low【u】值维护成low【0】,所以这个0号节点就是这个关键点:能够遍历其属强连通分量的起始点而且这样的起始点一定只有一个,所以只要发现了一个这样的关键起始点,那么就一定发现了一个强连通分量。而且这个节点没有其他点属于其他强连通分量能够有一条有向路径连到这个节点来的节点:如果这样的点存在,那么这些个点应该属于同一个强连通分量。


那么综上所述,相信大家也就能够理解为什么dfn【u】==low【u】的时候,我们就可以判断我们发现了一个强连通分量了。


代码实现:

void Tarjan(int u)//此代码仅供参考  
{  
    vis[u]=1;  
    low[u]=dfn[u]=cnt++;  
    for(int i=0;i<mp[u].size();i++)  
    {  
        int v=mp[u][i];  
        if(vis[v]==0)Tarjan(v);  
        if(vis[v]==1)low[u]=min(low[u],low[v]);  
    }  
    if(dfn[u]==low[u])  
    {  
        sig++;  
    }  
}  
然后再给一份完整代码,附加两组数据,大家可以参考一下:
#include<stdio.h>//此代码仅供参考,用于求一个图存在多少个强连通分量  
#include<string.h>  
#include<vector>  
#include<algorithm>  
using namespace std;  
#define maxn 1000000  
vector<int >mp[maxn];  
int ans[maxn];  
int vis[maxn];  
int dfn[maxn];  
int low[maxn];  
int n,m,tt,cnt,sig;  
void init()  
{  
    memset(low,0,sizeof(low));  
    memset(dfn,0,sizeof(dfn));  
    memset(vis,0,sizeof(vis));  
    for(int i=1;i<=n;i++)mp[i].clear();  
}  
void Tarjan(int u)  
{  
    vis[u]=1;  
    low[u]=dfn[u]=cnt++;  
    for(int i=0;i<mp[u].size();i++)  
    {  
        int v=mp[u][i];  
        if(vis[v]==0)Tarjan(v);  
        if(vis[v]==1)low[u]=min(low[u],low[v]);  
    }  
    if(dfn[u]==low[u])  
    {  
        sig++;  
    }  
}  
void Slove()  
{  
    tt=-1;cnt=1;sig=0;  
    for(int i=1;i<=n;i++)  
    {  
        if(vis[i]==0)  
        {  
            Tarjan(i);  
        }  
    }  
    printf("%d\n",sig);  
}  
int main()  
{  
    while(~scanf("%d",&n))  
    {  
        if(n==0)break;  
        scanf("%d",&m);  
        init();  
        for(int i=0;i<m;i++)  
        {  
            int x,y;  
            scanf("%d%d",&x,&y);  
            mp[x].push_back(y);  
        }  
        Slove();  
    }  

接下来我们讨论一下Tarjan算法能够干一些什么:

既然我们知道,Tarjan算法相当于在一个有向图中找有向环,那么我们Tarjan算法最直接的能力就是缩点辣!缩点基于一种染色实现,我们在Dfs的过程中,尝试把属于同一个强连通分量的点都染成一个颜色,那么同一个颜色的点,就相当于一个点。比如刚才的实例图中缩点之后就可以变成这样:



将一个有向带环图变成了一个有向无环图(DAG图)。很多算法要基于有向无环图才能进行的算法就需要使用Tarjan算法实现染色缩点,建一个DAG图然后再进行算法处理。在这种场合,Tarjan算法就有了很大的用武之地辣!


那么这个时候 ,我们再引入一个数组color【i】表示节点i的颜色,再引入一个数组stack【】实现一个栈,然后在Dfs过程中每一次遇到点都将点入栈,在每一次遇到关键点的时候将栈内元素弹出,一直弹到栈顶元素是关键点的时候为止,对这些弹出来的元素进行染色即可。


代码实现:


void Tarjan(int u)//此代码仅供参考  
{  
    vis[u]=1;  
    low[u]=dfn[u]=cnt++;  
    stack[++tt]=u;  
    for(int i=0;i<mp[u].size();i++)  
    {  
        int v=mp[u][i];  
        if(vis[v]==0)Tarjan(v);  
        if(vis[v]==1)low[u]=min(low[u],low[v]);  
    }  
    if(dfn[u]==low[u])  
    {  
        sig++;  
        do  
        {  
            low[stack[tt]]=sig;  
            color[stack[tt]]=sig;  
            vis[stack[tt]]=-1;  
        }  
        while(stack[tt--]!=u);  
    }  
}  

最后我们再上一道例题供大家学习:

Poj 2553

The Bottom of a Graph

Time Limit: 3000MS

 

Memory Limit: 65536K

Total Submissions: 10099

 

Accepted: 4175

Description

We will use the following (standard) definitions from graph theory. Let V be a nonempty and finite set, its elements being called vertices (or nodes). Let E be a subset of the Cartesian product V×V, its elements being called edges. Then G=(V,E) is called a directed graph. 
Let n be a positive integer, and let p=(e1,...,en) be a sequence of length n of edges ei∈E such that ei=(vi,vi+1) for a sequence of vertices (v1,...,vn+1). Then p is called a path from vertex v1 to vertex vn+1 in G and we say that vn+1 is reachable from v1, writing (v1→vn+1)
Here are some new definitions. A node v in a graph G=(V,E) is called a sink, if for every node w in G that is reachable from vv is also reachable from w. The bottom of a graph is the subset of all nodes that are sinks, i.e.,bottom(G)={v∈V|∀w∈V:(v→w)⇒(w→v)}. You have to calculate the bottom of certain graphs.

Input

The input contains several test cases, each of which corresponds to a directed graph G. Each test case starts with an integer number v, denoting the number of vertices of G=(V,E), where the vertices will be identified by the integer numbers in the set V={1,...,v}. You may assume that 1<=v<=5000. That is followed by a non-negative integer e and, thereafter, e pairs of vertex identifiers v1,w1,...,ve,we with the meaning that (vi,wi)∈E. There are no edges other than specified by these pairs. The last test case is followed by a zero.

Output

For each test case output the bottom of the specified graph on a single line. To this end, print the numbers of all nodes that are sinks in sorted order separated by a single space character. If the bottom is empty, print an empty line.

Sample Input

3 3

1 3 2 3 3 1

2 1

1 2

0

Sample Output

1 3

2

Source

Ulm Local 2003


题目大意:给你一堆点,一堆边,让你找到缩点之后出度为0的节点, 然后将节点编号从小到大排序输出。


思路:Tarjan,缩点染色,判断出度为0的强连通分量,将整个集合排序,输出即可。


AC代码:

#include<stdio.h>  
#include<string.h>  
#include<vector>  
#include<algorithm>  
using namespace std;  
#define maxn 1000000  
vector<int >mp[maxn];  
int ans[maxn];  
int degree[maxn];  
int color[maxn];  
int stack[maxn];  
int vis[maxn];  
int dfn[maxn];  
int low[maxn];  
int n,m,tt,cnt,sig;  
void init()  
{  
    memset(degree,0,sizeof(degree));  
    memset(color,0,sizeof(color));  
    memset(stack,0,sizeof(stack));  
    memset(low,0,sizeof(low));  
    memset(dfn,0,sizeof(dfn));  
    memset(vis,0,sizeof(vis));  
    for(int i=1;i<=n;i++)mp[i].clear();  
}  
void Tarjan(int u)  
{  
    vis[u]=1;  
    low[u]=dfn[u]=cnt++;  
    stack[++tt]=u;  
    for(int i=0;i<mp[u].size();i++)  
    {  
        int v=mp[u][i];  
        if(vis[v]==0)Tarjan(v);  
        if(vis[v]==1)low[u]=min(low[u],low[v]);  
    }  
    if(dfn[u]==low[u])  
    {  
        sig++;  
        do  
        {  
            color[stack[tt]]=sig;  
            vis[stack[tt]]=-1;  
        }  
        while(stack[tt--]!=u);  
    }  
}  
void Slove()  
{  
    tt=-1;cnt=1;sig=0;  
    for(int i=1;i<=n;i++)  
    {  
        if(vis[i]==0)  
        {  
            Tarjan(i);  
        }  
    }  
    for(int i=1;i<=n;i++)  
    {  
        for(int j=0;j<mp[i].size();j++)  
        {  
            int v=mp[i][j];  
            if(color[i]!=color[v])  
            {  
                degree[color[i]]++;  
            }  
        }  
    }  
    int tot=0;  
    for(int i=1;i<=sig;i++)  
    {  
        if(degree[i]>0)continue;  
        for(int j=1;j<=n;j++)  
        {  
            if(color[j]==i)  
            {  
                ans[tot++]=j;  
            }  
        }  
    }  
    sort(ans,ans+tot);  
    for(int i=0;i<tot;i++)  
    {  
        if(i==0)printf("%d",ans[i]);  
        else printf(" %d",ans[i]);  
    }  
    printf("\n");  
}  
int main()  
{  
    while(~scanf("%d",&n))  
    {  
        if(n==0)break;  
        scanf("%d",&m);  
        init();  
        for(int i=0;i<m;i++)  
        {  
            int x,y;  
            scanf("%d%d",&x,&y);  
            mp[x].push_back(y);  
        }  
        Slove();  
    }  
}


查看评论

强连通分量及缩点tarjan算法解析

强连通分量: 简言之 就是找环(每条边只走一次,两两可达) 孤立的一个点也是一个连通分量   使用tarjan算法 在嵌套的多个环中优先得到最大环( 最小环就是每个孤立点)   定义: int Ti...
  • qq574857122
  • qq574857122
  • 2013-11-16 22:49:41
  • 11809

POJ 2186 Popular Cows(强连通分量缩点,Tarjan算法)

【题目链接】 http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=16578 【解题报告】 【参考题解】 http://bl...
  • gungnir0711
  • gungnir0711
  • 2015-11-16 16:19:34
  • 390

强连通算法--Tarjan个人理解+详解

首先我们引入定义: 1、有向图G中,以顶点v为起点的弧的数目称为v的出度,记做deg+(v);以顶点v为终点的弧的数目称为v的入度,记做deg-(v)。 2、如果在有向图G中,有一条有向道路,则v称为...
  • mengxiang000000
  • mengxiang000000
  • 2016-06-14 17:31:12
  • 6152

tarjan模板(缩点,求有向图强连通分量)

整理出了这个tarjan模板,具体数组的功能代码都有注释。 const int N=100010; struct data { int to,next; } tu[N*2]; int head...
  • martinue
  • martinue
  • 2016-05-04 15:53:49
  • 1693

对于Tarjan强连通分量算法的理解

对于Tarjan强连通分量算法的理解
  • Goseqh
  • Goseqh
  • 2017-02-22 12:39:17
  • 1347

消息扩散(Tarjan算法缩点处理)

P2002 消息扩散 题目概述 给定一张有向图,不保证无自环与重边,信息从某几个节点出发,沿单向路传播,现在给出n个节点及其之间的道路,问至少需要在几个节点发布信息才能让这所有节点都得到信息。 ...
  • Stockholm_Sun
  • Stockholm_Sun
  • 2017-08-11 10:33:10
  • 357

POJ 2186【Tarjan算法(模板_缩点)】

//在一张有向无环图G,图G会包含很多环(环里面的点是等价的), //当然可以把环缩成一个点(利用tarjan缩点), //形成一棵树,题目要求是求除他以外的点都指向他,也就是只有一个叶子。 //因为...
  • KEYboarderQQ
  • KEYboarderQQ
  • 2016-05-15 10:13:25
  • 1025

Tarjan算法-强连通分量-题集

Tarjan算法用于求一个有向图里的强连通分量有哪些?经常用于在有环图里把环压成一个点,实现战略上无环化,毕竟环一定是强连通分量。...
  • u012469987
  • u012469987
  • 2016-05-02 01:34:02
  • 1467

tarjan算法缩点构图(模板)

tarjan算法简介请移步:tarjan简单介绍 tarjan在图论中算是一个很基础但是用处又很广泛的一个算法,这篇文章主要总结tarjan算法中关于缩点的模板。 用处:我们通过tarjan算法,将所...
  • qq_29980371
  • qq_29980371
  • 2017-09-13 10:34:04
  • 164

Tarjan算法求强连通分量总结

Tarjan算法求强连通分量总结 首先明确强连通分量的概念:如果图中的任意两个点都能互相到达,则为强连通分量。极大强连通分量:不被其它任何强连通分量包含的强连通分量。 强连通分量主要与两种边有关:...
  • MrH929
  • MrH929
  • 2016-07-18 19:12:21
  • 630
    个人资料
    持之以恒
    等级:
    访问量: 9743
    积分: 1487
    排名: 3万+
    最新评论