有向图的强连通分量(SCC)算法
强联通分量定义
给定有向图 G ( V , E ) G(V,E) G(V,E);
若 u , v ∈ V u,v\in V u,v∈V,我们定义函数 J [ u ] [ v ] J[u][v] J[u][v],若 u u u可以到达 v v v,则有 J [ u ] [ v ] = 1 J[u][v]=1 J[u][v]=1,否则 J [ u ] [ v ] = 0 J[u][v]=0 J[u][v]=0;
对于非空点集 C i ⊂ V C_i\subset V Ci⊂V,且 ∀ u , v ∈ C i , J [ u ] [ v ] = J [ v ] [ u ] = 1 \forall u,v\in C_i,J[u][v]=J[v][u]=1 ∀u,v∈Ci,J[u][v]=J[v][u]=1, ∀ u ∉ C i , 有 ∀ v ∈ C i J [ u ] [ v ] + J [ v ] [ u ] < 2 \forall u\notin C_i,有\forall v\in C_i J[u][v]+J[v][u]<2 ∀u∈/Ci,有∀v∈CiJ[u][v]+J[v][u]<2,称 C i C_i Ci为图的一个强联通分量SCC;
对于图的所有强联通分量 C 1 , C 2 , C 3 . . . . , C k C_1,C_2,C_3....,C_k C1,C2,C3....,Ck,我们可以将每一个强联通分量视为一个节点,若 ∃ u , v , u ∈ C i , v ∈ C j , i ≠ j \exist u,v,u\in C_i,v\in C_j,i\neq j ∃u,v,u∈Ci,v∈Cj,i=j有$ J[u][v]=1 , 则 我 们 对 ,则我们对 ,则我们对C_i,C_j 建 立 一 条 单 向 边 , 即 记 建立一条单向边,即记 建立一条单向边,即记J[C_i][C_j]=1 ; 我 们 记 新 形 成 的 图 为 ;我们记新形成的图为 ;我们记新形成的图为G^{’}(V,E)$,根据强联通分量的定义,新图是有向无环图;
T a r j a n Tarjan Tarjan算法
我们可以利用新建的 G ′ ( V , E ) G'(V,E) G′(V,E)是有向无环图的性质,通过先访问出度为0的节点,来实现 S C C SCC SCC的查询。最直接的做法是先反向建图,然后进行深搜,计算节点的完成时间,按照节点完成时间的升序,在原图上进行遍历,每次遍历到的连通块便是一个强连通分量。但是这样便需要对图进行两次遍历,于是我们可以还是通过建立新图的思想来实现 S C C SCC SCC的查找,我们利用深度优先搜索的特性,对于任意一个强连通分量而言,必然存在一个时刻强联通分量的点都在栈内,并且栈内元素都可以遍历到第一个入栈的点,所以我们对于每个节点可以记录一个low值来表示该节点能够到遍历到的最早进入栈中的元素的入栈时间戳是多少。那么,我们可以将最早入栈的元素视为这个强连通分量的代表元素,即最早入栈元素完成搜索后,其自身到栈顶便是一个强连通分量。
Tarjan(int s){
use[s]=1;dfn[s]=low[s]=++tim;sta.push(s);
for(int i=head[i];i;i=edge[i].pre){
int to=edge[i].to;
if(!use[s]&&dfn[s]) continue;
if(!dfn[s]) Tarjan(to);
low[s]=min(low[s],low[to]);
}
if(low[s]!=dfn[s]) return;
++cl;
int now=sta.top();
while(low[now]!=dfn[now]){
use[now]=0;
col[now]=cl;
sta.pop();
now=sta.top();
}
use[now]=0;
col[now]=cl;
sta.pop();
}