浅析强连通分量(Tarjan和kosaraju)
理解
在有向图G中,如果两点互相可达,则称这两个点强连通,如果G中任意两点互相可达,则称G是强连通图。
定理: 1、一个有向图是强连通的,当且仅当G中有一个回路,它至少包含每个节点一次。
2、非强连通有向图的极大强连通子图,称为强连通分量(SCC即Strongly Connected Componenet)。

在上图中,{1,2,3,4}是一个强连通分量,{5},{6}分别是另外两个强连通分量。怎么判断一个图是否是强连通图,如果不是,有哪些强连通分量,又怎么使它成为强连通图呢?
方法1:Korasaju算法
首先理解一下转置图的定义:将有向图G中的每一条边反向形成的图称为G的转置G T 。(注意到原图和G T 的强连通分支是一样的)
算法流程:
1.深度优先遍历G,算出每个结点u的结束时间f[u],起点如何选择无所谓。
2.深度优先遍历G的转置图G T ,选择遍历的起点时,按照结点的结束时间从大到小进行。遍历的过程中,一边遍历,一边给结点做分类标记,每找到一个新的起点,分类标记值就加1。
3. 第2步中产生的标记值相同的结点构成深度优先森林中的一棵树,也即一个强连通分量
注意:
Kosaraju算法比Tarjan时间复杂度要高,应用范围小,还有着爆栈超内存的风险,但这个算法比Tarjan好理解很多。当然和Tarjan一样,Kosaraju也只能用于有向图中。
Kosaraju也是基于深度优先搜索的算法。这个算法牵扯到两个概念,发现时间st,完成时间et。发现时间是指一个节点第一次被遍历到时的次序号,完成时间是指某一结点最后一次被遍历到的次序号。
在加边时把有向图正向建造完毕后再反向加边建一张逆图。
先对正图进行一遍dfs,遇到没访问过的点就让其发现时间等于目前的dfs次序号。在回溯时若发现某一结点的子树全部被遍历完,就让其完成时间等于目前dfs次序号。正图遍历完后将节点按完成时间入栈,保证栈顶是完成时间最大的节点,栈底是完成时间最小的节点。然后从栈顶开始向下每一个没有被反向遍历过的节点为起点对逆图进行一遍dfs,将访问到的点记录下来(或染色)并弹栈,每一遍反向dfs遍历到的点就构成一个强连通分量。
图解:

(a)为有向图G,
其中的阴影部分
是G的强连通分
支,对每个顶点
都标出了其发现
时刻与完成时刻
,黑色边为深度
优先搜索的树
枝;
(b)G的转置图G T
依次以b,c,g,h
为起点做DFS,
得到4个强连通
分量
算法复杂度分析
深度优先搜索的复杂度:Θ(V + E)
计算G T 的复杂度:0或者Θ(V + E)(临接表)
所以总的复杂度为:Θ(V + E)
非常好的算法!(个人更青睐于Tarjan,但kosaraju的思路至少得理解)
模板:
void positive_dfs(int pos){
DFN++;
vis[pos]=;
for(int i=pre[][pos];i;i=E[][i].next)
if(!vis[E[][i].to])
positive_dfs(E[][i].to);
stack[N*+-(++DFN)]=pos;
}
void negative_dfs(int pos){
dye[pos]=CN;
vis[pos]=;
size[dye[pos]]++;
for(int i=pre[][pos];i;i=E[][i].next)
if(vis[E[][i].to])
negative_dfs(E[][i].to);
}
int main(){
......
for(int i=;i<=N;i++)
if(!vis[i])
positive_dfs(i);
for(int i=;i<=N*;i++)
if(stack[i]&&vis[stack[i]]){
CN++;
negative_dfs(stack[i]);
}
......
}
方法二:Tarjan算法
理解:
Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。总的来说, Tarjan算法基于一个观察,即:同处于一个SCC中的结点必然构成DFS树的一棵子树。 我们要找SCC,就得找到它在DFS树上的根。
算法思想如下:
dfn[u]表示dfs时达到顶点u的次序号(时间戳),low[u]表示以u为根节点的dfs树中次序号最小的顶点的次序号,所以当dfn[u]=low[u]时,以u为根的搜索子树上所有节点是一个强连通分量。 先将顶点u入栈,dfn[u]=low[u]=++idx,扫描u能到达的顶点v,如果v没有被访问过,则dfs(v),low[u]=min(low[u],low[v]),如果v在栈里,low[u]=min(low[u],dfn[v]),扫描完v以后,如果dfn[u]=low[u],则将u及其以上顶点出栈。
图解(一定要仔细从左往右看):












模板(Tarjan算法):
void tarjan(int pos){
vis[stack[++index]=pos]=;//入栈并标记
LOW[pos]=DFN[pos]=++dfs_num;
for(int i=pre[pos];i;i=E[i].next){
if(!DFN[E[i].to]){
tarjan(E[i].to);
LOW[pos]=min(LOW[pos],LOW[E[i].to]);
}
else if(vis[E[i].to]) LOW[pos]=min(LOW[pos],DFN[E[i].to]);
}
if(LOW[pos]==DFN[pos]){
vis[pos]=;
size[dye[pos]=++CN]++;//染色及记录强连通分量大小
while(pos!=stack[index]){
vis[stack[index]]=;
size[CN]++;//记录大小
dye[stack[index--]]=CN;//弹栈并染色
}
index--;
}
}
模板(完整Tarjan):
#include <cstdio>
#include <stack>
#include <cstring>
#include <iostream>
using namespace std;
int n,m,idx=,k=,Bcnt=;
int head[];
int ins[]={};
int dfn[]={},low[]={};
int Belong[];
stack <int> s;
struct edge
{
int v,next;
}e[];
int min(int a,int b)
{
return a<b?a:b;
}
void adde(int u,int v)
{
e[k].v=v;
e[k].next=head[u];
head[u]=k++;
}
void readdata()
{
int a,b;
memset(head,-,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=;i<=m;i++)
{
scanf("%d%d",&a,&b);
adde(a,b);
}
}
void tarjan(int u)
{
int v;
dfn[u]=low[u]=++idx;//每次dfs,u的次序号增加1
s.push(u);//将u入栈
ins[u]=;//标记u在栈内
for(int i=head[u];i!=-;i=e[i].next)//访问从u出发的边
{
v=e[i].v;
if(!dfn[v])//如果v没被处理过
{
tarjan(v);//dfs(v)
low[u]=min(low[u],low[v]);//u点能到达的最小次序号是它自己能到达点的最小次序号和连接点v能到达点的最小次序号中较小的
}
else if(ins[v])low[u]=min(low[u],dfn[v]);//如果v在栈内,u点能到达的最小次序号是它自己能到达点的最小次序号和v的次序号中较小的
}
if(dfn[u]==low[u])
{
Bcnt++;
do
{
v=s.top();
s.pop();
ins[v]=;
Belong[v]=Bcnt;
}while(u != v);
}
}
void work()
{
for(int i=;i<=n;i++)if(!dfn[i])tarjan(i);
printf("\n");
for(int i = ;i <= ;i++)printf("%d %d\n",dfn[i],low[i]);
printf("共有%d强连通分量,它们是:\n",Bcnt);
for(int i=;i<=Bcnt;i++)
{
printf("第%d个:",i);
for(int j=;j<=n;j++)
{
if(Belong[j]==i)printf("%d ",j);
}
printf("\n");
}
}
int main()
{
readdata();
work();
return ;
}
/*
6 8
1 2
1 3
2 4
3 4
3 5
4 1
4 6
5 6
*/
至于例题,~~博主太懒,自己去找吧,推荐codevs1332 上白泽慧音和洛谷 受欢迎的牛
浅析强连通分量(Tarjan和kosaraju)的更多相关文章
- 算法数据结构 | 三个步骤完成强连通分量分解的Kosaraju算法
强连通分量分解的Kosaraju算法 今天是算法数据结构专题的第35篇文章,我们来聊聊图论当中的强连通分量分解的Tarjan算法. Kosaraju算法一看这个名字很奇怪就可以猜到它也是一个根据人名起 ...
- 强连通分量(tarjan求强连通分量)
双DFS方法就是正dfs扫一遍,然后将边反向dfs扫一遍.<挑战程序设计>上有说明. 双dfs代码: #include <iostream> #include <cstd ...
- POJ2186 Popular Cows 【强连通分量】+【Kosaraju】+【Tarjan】+【Garbow】
Popular Cows Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 23445 Accepted: 9605 Des ...
- 强连通分量的模版 Kosaraju+Tarjan+Garbow
PS:在贴出代码之前,我得说明内容来源——哈尔滨工业大学出版的<图论及应用>.虽然有一些错误的地方,但是不得不说是初学者该用的书. 从效率的角度来说,Kosaraju <Tarjan ...
- 有向图强连通分量 Tarjan算法
[有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极 ...
- POJ2186 Popular Cows 强连通分量tarjan
做这题主要是为了学习一下tarjan的强连通分量,因为包括桥,双连通分量,强连通分量很多的求法其实都可以源于tarjan的这种方法,通过一个low,pre数组求出来. 题意:给你许多的A->B ...
- 图之强连通、强连通图、强连通分量 Tarjan算法
原文地址:https://blog.csdn.net/qq_16234613/article/details/77431043 一.解释 在有向图G中,如果两个顶点间至少存在一条互相可达路径,称两个顶 ...
- (转)求有向图的强连通分量个数(kosaraju算法)
有向图的连通分量的求解思路 kosaraju算法 逛了很多博客,感觉都很难懂,终于找到一篇能看懂的,摘要记录一下 原博客https://www.cnblogs.com/nullzx/p/6437926 ...
- [有向图的强连通分量][Tarjan算法]
https://www.byvoid.com/blog/scc-tarjan 主要思想 Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树.搜索时,把当前搜索树中未处理的 ...
随机推荐
- 现在开始学习WPF了,mongodb在整理一下
回忆一下自己学习mongodb的过程 1安装 2增删改查 3数据类型转换 4GridFS 5权限管理--开启权限之前先建立一个超级用户(admin库中),开启权限,用该用户登陆,进入admin数据库( ...
- [2012-08-06]awk多文件合并并按文件名分段
以下代码满足这样的需求: 多个文件内容合并到一个文件A中(如果没有下面这条,使用cat就能解决) 文件A中每段内容之前保留原先的文件名 awk 'tmp!=FILENAME{tmp=FILENAME; ...
- 增强遍历和Object多参数遍历
public class T2 { public void t1(Object o){//Object是任何类型,多态 System.out.println(o.toString()); } publ ...
- django框架(Model)
-------------------使用MySql数据库-------------------1.进行对应mysql-python包的下载 pip install mysql-python 2.在m ...
- 一、Solr的相关概念了解
1.1. 什么是Solr Solr 是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文搜索服务器.Solr提供了比Lucene更为丰富的查询语言,同时实现了可配置.可扩展 ...
- awk知识点全回顾
本文目录:1.awk简介和基本语法格式2.print和printf格式化输出3.输入行的字段分隔符和行分隔符4.BGEIN和END5.数组6.流程控制语句 6.1 条件判断语句 6.2 while循环 ...
- Java基础学习 —— 对象的克隆
对象的克隆分为对象的浅克隆和深克隆 一.对象的浅克隆 注意事项: 1.如果一个对象需要调用clone的方法克隆,那么该对象必须要实现Cloneable接口 2.Cloneable对象只是一个标识对象, ...
- 第4阶段——制作根文件系统之编译配置安装busybox(3)
在上一节分析出制作一个最小的根文件系统至少需要: (1)/dev/console(终端控制台, 提供标准输入.标准输出以及标准错误) /dev/null (为空的话就是/dev/null, 所有写到 ...
- IOS学习【VMware 12 安装 Mac OS X 10.11】-Day1
1.下载安装VMware 12 pro (32位不能安装) 虚拟机 VM12密钥 5A02H-AU243-TZJ49-GTC7K-3C61N 2.unlock206 用于VMware 12识别tac ...
- 编程零基础应当如何开始学习 Python?
提前说一下,这篇福利多多,别的不说,直接让你玩回最有手感的怀旧游戏,参数贴图很方便自己可以根据喜好修改哦. 本篇通过以下四块展开,提供大量资源对应. 选一个好版本 有没有看过<在下坂本,有何贵干 ...