强连通分量的三个求法

这里主要谈及强连通分量(以下简称SCC,strongly connected component)三种常见的求法(以下涉及的图均为有向图),即Kosaraju、Tarjan和Gabow。三种算法背后的基础思想都是DFS,只是它们通过DFS获得了不同的信息。各位大哥大姐继续往下读之前,最好对DFS相关的概念和性质比较熟悉,例如,什么叫做<a title="DFS" href="http://en.wikipedia.org/wiki/Depth-firstsearch" target="blank">反向边、交叉边</a>。

Kosaraju

我就不八卦这大哥了。Kosaraju的方法也就是导论第二版中文22章中讲的方法, 即广为人知的两遍DFS。Kosaraju算法说白了就是先对原图来一遍DFS, 再把所有边的方向都倒过来,按照刚才DFS求出的结点完成时间的逆序,再来一遍DFS。

那么为啥可以这么做呢?

这就需要一个性质来帮忙,即当A、B为两个SCC,且存在有向边(a, b),其中a∈A,b∈B, 那么必然有:A的完成时间晚于B(一个SCC的完成时间表示该SCC中所有结点完成时间最晚的一个)。

可以简单证明一下:

如果我们在第一遍DFS的时候,A先于B被访问,且A中的第一个被访问到的结点是x, 那A和B中所有的结点显然都能在x之后被访问到。于是x的完成时间要晚于B中任何一个结点, 从而A的完成时间晚于B。

如果B先于A被访问,由于A和B是不同的两个SCC,而且只有A到B的边,于是B就不能到达A。 那么当B中的结点被访问完之后,A中的点仍然处于为访问状态,自然A的完成时间也就晚于B了。

所以在第二遍DFS时,第一次取到的那个完成时间最晚的结点u, 它所在的SCC在转置图中就不能有指向外的边。于是对转置图的第二遍DFS, 从u开始,便能轻易走遍所有处于同一SCC的结点。后续的遍历步骤也就类似了。

时间复杂度自然是算在两次DFS头上,O(V+E)。

Tarjan

Tarjan貌似跟Hopcroft都是Cornell的大神。总的来说, Tarjan算法基于一个观察,即:同处于一个SCC中的结点必然构成DFS树的一棵子树。 我们要找SCC,就得找到它在DFS树上的根。

那么怎么找呢?

考虑一下,如果DFS访问到了某个结点u,又顺着u来到了结点v, 但从v发出了一条反向边,指向了u的前驱w,那根据DFS的性质, u->v->w->u构成了一个环。这一堆东西必然处于同一个SCC。 所以某个要找到SCC子树的根,就得找那个在DFS树中最早被发现的结点,且这个结点要与它的一堆后继结点形成环。

这时候DFS的特性就派上用场了。最早发现的结点可以通过记录发现时间来实现,而反向边的判断可以通过结点颜色,即访问状态来实现。 定义一个结点的low值为:从该节点的子树结点可达的,尚未求出属于哪个SCC的结点的最早访问时间。 由于SCC构成子树,所以求没求出某个结点所在的SCC用栈来刻画就可以了: 每次访问到一个结点u,记录发现时间visit,并将它推到栈里去。 如果从u可达的结点v没访问过,那么访问v,用v的low值更新u; 否则,如果v已访问过,那就看看它在不在栈中。如果在,说明还没确定v到底属于哪个SCC, 这时(u, v)就是一条反向边了,根据v的visit值,更新u的low值即可。 最后回到u结点时,如果u的low值和visit相等了,显然u就是我们要找的根节点了。 从栈里把u和其上所有结点弹出来,这一堆东西就在一个SCC里了。上伪代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

// n: number of nodes in the graph

// visit[i]: discovery time of node i
// low[i]: low-value of node i
// time: the time stamp
// S: the stack

time <- 1

FIND_SCC(G):
for i<-1 to n:
    if visit[i] is not defined:
        TARJAN(i)

TARJAN(u):
visit[u] <- time
low[u] <- time
time <- time + 1
push(S, u)

for each edge (u, i) in the graph:
    if visit[i] is not defined:
        TARJAN(i)
        low[u] <- min{ low[u], low[i] }
    else if i is in S:
        low[u] <- min{ low[u], visit[i] }

// things popped here are in the same SCC
if visit[u] == low[u]:
    pop all node above u on stack including u

每个结点入栈一次出栈一次,每条边访问一次,O(V+E)。

Gabow

Gabow与Tarjan的思路是一致的。但Gabow使用了另一个栈来找出SCC子树的根。 Gabow使用的栈S与Tarjan一样,保存尚未决定属于哪个SCC的结点; 栈P保持如下性质:栈顶结点始终具有最小的visit值, 即保持栈顶元素的visit值小于等于当前发现的反向边指向的祖先结点的visit值。

栈S和P都随着DFS的进行增长。若当前正在访问结点u,从u可达点v, 先将u压入两个栈中。这一步骤相当于Tarjan中初始化一个结点的low值为当前visit值。 如果v没有访问过,则访问v;否则判断v是否在S栈中。 如果在,那么(u, v)为反向边,此时从P栈顶弹出那些晚于v被发现的结点。为啥? 因为此时v是u的后继结点,我们得找出以u为根的子树结点能到达的最早访问的结点, 类似于Tarjan算法中对low值的更新。

再一次回到u时,若P栈栈顶的元素就是u,表明u就是SCC子树的根。 与Tarjan类似,从S栈中弹出元素即找到了一个SCC,代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

GABOW(u):
visit[u] <- time
time <- time + 1
push(S, u)
push(P, u)

for each edge (u, i) in the graph:
    if visit[i] is not defined:
        GABOW(i)
    else if i is in S:
        repeat popping nodes on P until visit[top(P)] <= visit[i]

// things popped here are in the same SCC
if top(P) == u:
    pop all node above u on S including u
    pop u from P

Gabow时间复杂度也为O(V+E),常数因子的差别各位大神请自行分析。

时间: 2024-10-26 17:00:52

强连通分量的三个求法的相关文章

强连通分量的Tarjan算法

资料参考 Tarjan算法寻找有向图的强连通分量 基于强联通的tarjan算法详解 有向图强连通分量的Tarjan算法 处理SCC(强连通分量问题)的Tarjan算法 强连通分量的三种算法分析 Tarjan算法详解理解集合 ppt图解分析下载 强连通分量 强连通分量(strongly connected component)是图论中的概念.图论中,强连通图指每一个顶点皆可以经由该图上的边抵达其他的每一个点的有向图.意即对于此图上每一个点对(Va,Vb),皆存在路径Va→Vb以及Vb→Va.强连通

tarjan——强连通分量+缩点

tarjan陪伴强联通分量 生成树完成后思路才闪光 欧拉跑过的七桥古塘 让你 心驰神往"----<膜你抄>   自从听完这首歌,我就对tarjan开始心驰神往了,不过由于之前水平不足,一直没有时间学习.这两天好不容易学会了,写篇博客,也算记录一下.   一.tarjan求强连通分量 1.什么是强连通分量? 引用来自度娘的一句话: "有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则

hihoCoder_#1185_连通性&#183;三&#183;强连通分量

#1185 : 连通性·三 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 暑假到了!!小Hi和小Ho为了体验生活,来到了住在大草原的约翰家.今天一大早,约翰因为有事要出去,就拜托小Hi和小Ho忙帮放牧. 约翰家一共有N个草场,每个草场有容量为W[i]的牧草,N个草场之间有M条单向的路径. 小Hi和小Ho需要将牛羊群赶到草场上,当他们吃完一个草场牧草后,继续前往其他草场.当没有可以到达的草场或是能够到达的草场都已经被吃光了之后,小hi和小Ho就把牛羊群赶回家. 一开

【强连通分量】

有向图的强连通分量 一.定义 给定一张有向图.若对于任意两个节点x,y 既存在从x->y的路径,也存在从y->x的路径,则称该有向图为“强连通图”. 有向图的极大连通子图被称为强连通分量. 二.强连通分量的求法. 1.Tarjan算法 基于 dfs 的一种算法,每一个强连通分量为其搜索树的一棵子树,并且需要用到 栈 的数据结构. tarjan 中两个重要的数组  dfn[] (时间戳,搜索时的序号) , low[] (表示当前能回溯到的最小的时间戳), 初始化  dfn[] = low[] =

Kosaraju算法解析: 求解图的强连通分量

1. 定义 连通分量:在无向图中,即为连通子图. 上图中,总共有四个连通分量.顶点A.B.C.D构成了一个连通分量,顶点E构成了一个连通分量,顶点F,G和H,I分别构成了两个连通分量. 强连通分量:有向图中,尽可能多的若干顶点组成的子图中,这些顶点都是相互可到达的,则这些顶点成为一个强连通分量. 上图中有三个强连通分量,分别是a.b.e以及f.g和c.d.h. 2. 连通分量的求解方法 对于一个无向图的连通分量,从连通分量的任意一个顶点开始,进行一次DFS,一定能遍历这个连通分量的所有顶点.所以

【强连通分量】tarjan算法及kosaraju算法+例题

阅读前请确保自己知道强连通分量是什么,本文不做赘述. Tarjan算法 一.算法简介 Tarjan算法是一种由Robert Tarjan提出的求有向图强连通分量的时间复杂度为O(n)的算法. 首先我们要知道两个概念:时间戳(DFN),节点能追溯到的最早的栈中节点的时间戳(LOW).顾名思义,DFN就是在搜索中某一节点被遍历到的次序号(dfs_num),LOW就是某一节点在栈中能追溯到的最早的父亲节点的搜索次序号. Tarjan算法是基于深度优先搜索的算法.在搜索过程中把没有Tarjan过的点入栈

TarJan 算法求解有向连通图强连通分量

[有向图强连通分量] 在有向图G中,如果两个 顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components). 下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达.{5},{6}也分别是两个强连通分量. 大体来说有3中算法Kosaraju,Trajan,Gabow这三种!后续文章中将相继

连通分量模板:tarjan: 求割点 &amp;&amp; 桥 &amp;&amp; 缩点 &amp;&amp; 强连通分量 &amp;&amp; 双连通分量 &amp;&amp; LCA(最近公共祖先)

PS:摘自一不知名的来自大神. 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合. 3.点连通度:最小割点集合中的顶点数. 4.割边(桥):删掉它之后,图必然会分裂为两个或两个以上的子图. 5.割边集合:如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合. 6.边连通度:一个图的边连通度的定义为,最

Kosaraju算法——强连通分量

′有向图的极大强连通子图,称为强连通分量. ′子图指的是选取V的一个子集V',以及E当中所有满足u,v∈V'的边集E'所指代的图. ′我们需要找出一幅有向图当中的所有强连通分量. ′一个最朴素的算法: ′构造一个传递闭包(也就是数组Aij表示i能否到达j),然后把Aij=Aji=1的节点置于同一个强连通分量当中 ′这个算法的复杂度是O(n^3),优点是代码复杂度小,缺点是速度太慢了 ′这个算法原理和上面的方法是类似的,如果A能到达B并且B能到达A,那么A和B在同一个强连通分量里面. ′也可以等同