强联通分量算法的个人详解Tarjan算法(包含缩点)
有向图强连通分量:在有向图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 v, v 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算法(包含缩点)相关推荐
- python2.7除法_对python中的float除法和整除法的实例详解
从python2.2开始,便有两种除法运算符:"/"."//".两者最大区别在: python2.2前的版本和python2.2以后3.0以前的版本的默认情况下 ...
- java二分查找法_java算法之二分查找法的实例详解
java算法之二分查找法的实例详解 原理 假定查找范围为一个有序数组(如升序排列),要从中查找某一元素,如果该元素在此数组中,则返回其索引,否则返回-1.通过数组长度可取出中间位置元素的索引,将其值与 ...
- 扩展Euclidean算法求乘法逆原理详解与算法实现
[利用扩展Euclidean算法求乘法逆] 1. Equipment (1) operating system version :WIN 10 (2) CPU instruction set: x 6 ...
- python实现连续变量最优分箱详解--CART算法
今天小编就为大家分享一篇python实现连续变量最优分箱详解–CART算法,具有很好的参考价值,希望对大家有所帮助.一起跟随小编过来看看吧 关于变量分箱主要分为两大类:有监督型和无监督型 对应的分箱方 ...
- 详解rsync算法--如何减少同步文件时的网络传输量
详解rsync算法--如何减少同步文件时的网络传输量 先看下图中的场景,客户端A和B,以及服务器server都保存了同一个文件,最初,A.B和server上的文件内容都是相同的(记为File.1).某 ...
- 【算法知识】详解堆排序算法
点击蓝色字关注我们! 什么是堆 「堆」首先是一个完全二叉树,「堆」分为「大顶堆」和「小顶堆」: 「大顶堆」 : 每个节点的值大于或等于其左右孩子节点的值,称为大顶堆. 「小顶堆」同理就是每个节点的值小 ...
- 【算法知识】详解基数排序算法
已发布: [算法知识]详解选择冒泡算法 [算法知识]详解选择排序算法 [算法知识]详解插入排序算法 [算法知识]详解快速排序算法 [算法知识]详解归并排序算法 基本思想 基数排序的思想是将整数按位数切 ...
- 【算法知识】详解归并排序算法
已发布: [算法知识]详解选择冒泡算法 [算法知识]详解选择排序算法 [算法知识]详解插入排序算法 [算法知识]详解快速排序算法 基本思想 归并排序的基本思想是: 先将序列一次次分成子序列,直到子序列 ...
- 【算法知识】详解快速排序算法
基本思想 已发布: [算法知识]详解选择冒泡算法 [算法知识]详解选择排序算法 [算法知识]详解插入排序算法 本文的思路是以从小到大为例讲的. 快速排序的基本思想是任取待排序序列的一个元素作为中心元素 ...
最新文章
- network 学习笔记
- Spring MVC 搭建web项目示例
- java redis自增操作_【转载】关于spring boot使用redis的increment()方法自增问题
- OpenCV图像处理使用笔记(三)——单通道的Mat对象强转为三通道的Mat对象
- C语言 编写程序,求s不超过2000时n的最大值,s=1+2+3+……+n
- CSS之expression
- java - springmvc整合cxf发布webservice
- 现代软件工程系列 学生读后感 梦断代码 DTSlob (2)
- 在html中标记bdo,HTML_HTML非常用标签 optgroup、sub、sup和bdo示例代码,optgroup 用在select 标记中 可以 - phpStudy...
- bzoj 1676: [Usaco2005 Feb]Feed Accounting 饲料计算(差分)
- 取本周日期 C# .net
- 1-关于单片机通信数据传输(中断发送,大小端,IEEE754浮点型格式,共用体,空闲中断,环形队列)...
- OpenCV MPR.DLL WNetRestoreConnectionA相关问题
- 写一个函数days,实现第一题的计算。由主函数将年月日传递给days函数。计算后将日子数传回主函数输出
- 图片批量合成PDF方法
- 身份证真伪的验证(C# / ASP.NET /JS代码)及算法说明
- echars x轴单位_ECharts xAxis配置 x坐标轴刻度设置
- 以标记清除的方式垃圾回收
- PAT - 浙江大学计算机程序设计能力考试简介
- 【vue】【开源】vue开源电商项目hello-mall嗨喽商城