理解

  在有向图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)的更多相关文章

  1. 算法数据结构 | 三个步骤完成强连通分量分解的Kosaraju算法

    强连通分量分解的Kosaraju算法 今天是算法数据结构专题的第35篇文章,我们来聊聊图论当中的强连通分量分解的Tarjan算法. Kosaraju算法一看这个名字很奇怪就可以猜到它也是一个根据人名起 ...

  2. 强连通分量(tarjan求强连通分量)

    双DFS方法就是正dfs扫一遍,然后将边反向dfs扫一遍.<挑战程序设计>上有说明. 双dfs代码: #include <iostream> #include <cstd ...

  3. POJ2186 Popular Cows 【强连通分量】+【Kosaraju】+【Tarjan】+【Garbow】

    Popular Cows Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 23445   Accepted: 9605 Des ...

  4. 强连通分量的模版 Kosaraju+Tarjan+Garbow

    PS:在贴出代码之前,我得说明内容来源——哈尔滨工业大学出版的<图论及应用>.虽然有一些错误的地方,但是不得不说是初学者该用的书. 从效率的角度来说,Kosaraju <Tarjan ...

  5. 有向图强连通分量 Tarjan算法

    [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极 ...

  6. POJ2186 Popular Cows 强连通分量tarjan

    做这题主要是为了学习一下tarjan的强连通分量,因为包括桥,双连通分量,强连通分量很多的求法其实都可以源于tarjan的这种方法,通过一个low,pre数组求出来. 题意:给你许多的A->B ...

  7. 图之强连通、强连通图、强连通分量 Tarjan算法

    原文地址:https://blog.csdn.net/qq_16234613/article/details/77431043 一.解释 在有向图G中,如果两个顶点间至少存在一条互相可达路径,称两个顶 ...

  8. (转)求有向图的强连通分量个数(kosaraju算法)

    有向图的连通分量的求解思路 kosaraju算法 逛了很多博客,感觉都很难懂,终于找到一篇能看懂的,摘要记录一下 原博客https://www.cnblogs.com/nullzx/p/6437926 ...

  9. [有向图的强连通分量][Tarjan算法]

    https://www.byvoid.com/blog/scc-tarjan 主要思想 Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树.搜索时,把当前搜索树中未处理的 ...

随机推荐

  1. jsp的C标签一般使用方法以及js接收servlet中的对象及对象数字

    jsp的C标签一般使用方法以及js接收servlet中的对象及对象数组 由于现流行的javaWeb框架提倡前后端分离,比如在SpringMvc中已经很少写servlet的一些东西:目前 前端jsp中大 ...

  2. 关于回文串的DP问题

    问题1:插入/删除字符使得原字符串变成一个回文串且代价最小 poj 3280 Cheapest Palindrome 题意:给出一个由m中字母组成的长度为n的串,给出m种字母添加和删除花费的代价,求让 ...

  3. MyEclipse简介

  4. linux命令详解(一)netstat

    今天在使用linux的时候,要查看端口号,但是不知道要使用哪一个命令所以就学习了一下,原来是使用netstat,接下来给大家一起来学习. 一.netstat介绍 1.1.简介 Netstat 命令用于 ...

  5. 工具类:将其他编码类型转换成UTF-8或者其他类型的工具类

    将其他编码类型转换成UTF-8或者其他类型的工具类 public static String changeUTF(String str) { String newStr = null; try { n ...

  6. Codis分布式锁

    近期一项需求需要使用分布式锁,考虑的方案主要有如下两种: zookeeper codis 因为对于zookeeper不是特别熟悉,因此选用了codis,Codis是一个分布式的Redis解决方案,从应 ...

  7. pycharm远程linux开发和调试代码

    pycharm是一个非常强大的python开发工具,现在很多代码最终在线上跑的环境都是linux,而开发环境可能还是windows下开发,这就需要经常在linux上进行调试,或者在linux对代码进行 ...

  8. IOS学习【xcode 7新特性url链接】

    由于xcode7的更新,在访问http链接的时候会输出错误信息 The resource could not be loaded because the App Transport Security ...

  9. tkinter第三章(单选和多选)RadioButton CheckButton

    最简单的CheckButton多选类 import tkinter as tk #checkButton的内容,多选 root = tk.Tk() v = tk.IntVar()#装整形变量的 #va ...

  10. (4)ES6解构赋值-字符串篇

    字符串的解构赋值 let [a,b,c,d,e] = 'Apple'; console.log(a); //A console.log(b); //p console.log(c); //p cons ...