有向图的强连通分量
在明白强连通分量之前,先要明白 强连通 与 强连通图 是什么
强连通
在图G中,互相连通的(U, V)两个点就叫它们两个点强连通
比如说
在此图中,A能到达C, C也能到达A,所以(A, C)强连通;B能到达A,但A不能到达B,所以(A,B)不是强连通
强连通图
在图G中,每个节点都能直接或间接连到其他的N-1个点,那么这个图就叫做强连通图
像下面这个图
在此图中,(A,B,C)都能连到其他任意一点
这时候,就会有聪明的小朋友说了:那B也没有线连到A呀!
注意,每个点都不一定直接连到其他点,可以间接地连到。比如这里的B,可以通过B–>C–>A来间接连到,所以,我们也叫(A,B)是可连通的。
强连通分量
在图G中,有一个强连通子图g,子图g就是强连通分量
在下面这个图中
点(A,B,C)组成的一个子图就是一个强连通分量
注意:一个节点也可以形成一个强连通分量
Tarjan求强连通分量
Tarjan算法运用了DFS(深度优先搜索)与栈的技术来求「强连通分量」
通常,我们使用
「
d
f
n
」
「dfn」
「dfn」与
「
l
o
w
」
「low」
「low」两个数组来实现Tarjan算法
- d f n dfn dfn数组: d f n dfn dfn数组记录了搜索树的搜索时间戳(人话:深搜的搜索顺序),也记录了某个节点是否被遍历过
- l o w low low数组: l o w low low数组记录了当前节点能访问到的当前搜索树中(栈中) 最早能访问到的节点编号,初始值与 d f n dfn dfn数组相同(搜索时间戳)
- 如果 d f n dfn dfn与 l o w low low数组的值相同,那么就表示存在一个强连通分量
上面的几句话看不懂也没有关系(其实作者初学时也不明白),下面让我们模拟一下tarjan的全过程
这里使用
D
F
N
/
L
O
W
DFN/LOW
DFN/LOW来表示各节点
首先遍历到节点 1 1 1
更新节点1的 d f n dfn dfn与 l o w low low,使用 t i m e r timer timer变量来记录时间戳
d f n [ 1 ] = l o w [ 1 ] = + + t i m e r dfn[1] = low[1] = ++timer dfn[1]=low[1]=++timer
将节点1的编号入栈,并标记它 : s t a c k . p u s h ( 1 ) , i n S t a c k [ 1 ] = t r u e stack.push(1), inStack[1] = true stack.push(1),inStack[1]=true
节点1被更新为
1
/
1
1/1
1/1
stack(栈):
1
1
1
然后遍历到节点
3
3
3,更新节点3为
2
/
2
2/2
2/2
d
f
n
[
3
]
=
l
o
w
[
3
]
=
+
+
t
i
m
e
r
;
s
t
a
c
k
.
p
u
s
h
(
3
)
,
i
n
S
t
a
c
k
[
3
]
=
t
r
u
e
dfn[3] = low[3] = ++timer; stack.push(3), inStack[3] = true
dfn[3]=low[3]=++timer;stack.push(3),inStack[3]=true
stack(栈):
1
,
3
1,3
1,3
更新节点5为
3
/
3
3/3
3/3
d
f
n
[
5
]
=
l
o
w
[
5
]
=
+
+
t
i
m
e
r
;
s
t
a
c
k
.
p
u
s
h
(
5
)
,
i
n
S
t
a
c
k
[
5
]
=
t
r
u
e
dfn[5] = low[5] = ++timer; stack.push(5), inStack[5] = true
dfn[5]=low[5]=++timer;stack.push(5),inStack[5]=true
stack(栈):
1
,
3
,
5
1,3,5
1,3,5
更新节点6为
4
/
4
4/4
4/4
d
f
n
[
6
]
=
l
o
w
[
6
]
=
+
+
t
i
m
e
r
;
s
t
a
c
k
.
p
u
s
h
(
6
)
,
i
n
S
t
a
c
k
[
6
]
=
t
r
u
e
dfn[6] = low[6] = ++timer; stack.push(6), inStack[6] = true
dfn[6]=low[6]=++timer;stack.push(6),inStack[6]=true
stack(栈):
1
,
3
,
5
,
6
1,3,5,6
1,3,5,6
节点6往后遍历,发现没有了
发现节点6的
d
f
n
[
6
]
=
l
o
w
[
6
]
dfn[6] = low[6]
dfn[6]=low[6] :
6
/
6
6/6
6/6
那么栈中节点6及节点6入栈后入栈的节点形成一个强连通分量
将它们记录,并将它们出栈
所以,stack更新为
:
1
,
3
,
5
:1,3,5
:1,3,5
{
6
6
6}为一个强连通分量
往前回溯到节点
5
5
5
l
o
w
[
6
]
>
l
o
w
[
5
]
low[6] > low[5]
low[6]>low[5],所以不更新节点5
节点
5
5
5没有其他可到达的点了
并且节点
5
5
5的
d
f
n
dfn
dfn与
l
o
w
low
low相同:
5
/
5
5/5
5/5
将
5
5
5与
5
5
5以后入栈的节点出栈并记录
更新stack
:
1
,
3
:1,3
:1,3
{
5
5
5}为强连通分量
回溯到节点
3
3
3,
l
o
w
[
5
]
>
l
o
w
[
3
]
low[5] > low[3]
low[5]>low[3],所以不更新节点3
继续遍历到节点
4
4
4
更新节点4为
5
/
5
5/5
5/5
d
f
n
[
4
]
=
l
o
w
[
4
]
=
+
+
t
i
m
e
r
;
s
t
a
c
k
.
p
u
s
h
(
4
)
,
i
n
S
t
a
c
k
[
4
]
=
t
r
u
e
dfn[4] = low[4] = ++timer; stack.push(4), inStack[4] = true
dfn[4]=low[4]=++timer;stack.push(4),inStack[4]=true
stack(栈):
1
,
3
,
4
1,3,4
1,3,4
继续遍历,连接到了节点
1
1
1
节点
1
1
1还在栈内(此搜索树内),并且节点
1
1
1比节点
4
4
4遍历的时间戳小,所以**更新当前节点
4
4
4的
l
o
w
low
low为节点
1
1
1的
d
f
n
dfn
dfn为
1
1
1
更新节点
4
4
4为
5
/
1
5/1
5/1
再次回溯至节点
3
3
3
l
o
w
[
4
]
<
l
o
w
[
3
]
low[4] < low[3]
low[4]<low[3],更新节点3的
l
o
w
low
low为节点4的
l
o
w
low
low:1
当前节点3为:
2
/
1
2/1
2/1
继续回溯至节点
1
1
1
l
o
w
[
3
]
=
=
l
o
w
[
1
]
low[3] == low[1]
low[3]==low[1],所以不更新节点
1
1
1
继续遍历至节点
2
2
2
更新节点
2
2
2为 :
6
/
6
6/6
6/6
d
f
n
[
2
]
=
l
o
w
[
2
]
=
+
+
t
i
m
e
r
;
s
t
a
c
k
.
p
u
s
h
(
2
)
,
i
n
S
t
a
c
k
[
2
]
=
t
r
u
e
dfn[2] = low[2] = ++timer; stack.push(2), inStack[2] = true
dfn[2]=low[2]=++timer;stack.push(2),inStack[2]=true
stack(栈):
1
,
3
,
4
,
2
1,3,4,2
1,3,4,2
继续遍历,连接到了节点
4
4
4
节点
4
4
4还在栈内,并且节点
4
4
4比节点
2
2
2遍历的时间戳小,所以更新当前节点
2
2
2的
l
o
w
low
low为节点
4
4
4的
d
f
n
dfn
dfn为
5
5
5
当前节点
4
4
4为
6
/
5
6/5
6/5
回溯至节点1
节点1没有其他节点可遍历了
并且节点
1
1
1的
d
f
n
dfn
dfn与
l
o
w
low
low相同:
1
/
1
1/1
1/1
将
1
1
1与
1
1
1以后入栈的节点出栈并记录
更新stack
:
(
空
)
:(空)
:(空)
{
1
,
3
,
4
,
2
1,3,4,2
1,3,4,2}为强连通分量
至此,这张图就遍历完成了
先把tarjan的模板放上来
void tarjan(int u){
dfn[u] = low[u] = ++timer;//初始化当前节点
s.push(u), vis[u] = 1;//进栈,并标记
for(int i = head[u]; i; i = e[i].nxt){//前向星存图
int v = e[i].v;
if(!dfn[v]){//如果目标节点没有遍历过
tarjan(v);
low[u] = min(low[u], low[v]);//更新当前节点
}else if(vis[v]){//否则如果目标节点还在栈中
low[u] = min(low[u], dfn[v]);//更新当前节点
}
}
if(low[u] == dfn[u]){//如果当前节点是强连通分量的根节点
int v;
ans++;//强连通分量个数加1
do{
v = s.top();
s.pop();//出栈
vis[v] = 0;//取消标记
scc[v] = ans;//记录当前强连通分量的节点
}while(u != v);
}
}
例题
CODE:
#include <bits/stdc++.h>
using namespace std;
int n, m, k, cnt, head[5005], p;
int dfn[5005], low[5005];
int t[5005], ans, ak;
stack<int> s;
bool vis[5005];
struct edge{
int v, nxt;
}e[1000005];
vector<int> g[5005];
void add(int u, int v){
e[++cnt] = edge{v, head[u]};
head[u] = cnt;
}
void tarjan(int u){
vis[u] = 1, dfn[u] = low[u] = ++k;
s.push(u);
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(!dfn[v]){
tarjan(v);
low[u] = min(low[u], low[v]);
}else if(vis[v]){
low[u] = min(low[u], dfn[v]);
}
}
if(low[u] == dfn[u]){
p++;
int v;
do{
v = s.top();
g[p].push_back(v);
vis[v] = 0;
s.pop();
}while(u != v);
}
}
int main(){
cin >> n >> m;
for(int i = 1, u, v, w; i <= m; ++i){
cin >> u >> v >> w;
add(u, v);
if(w == 2) add(v, u);
}
for(int i = 1; i <= n; ++i){
if(!dfn[i]) k = 0, tarjan(i);
}
for(int i = 1; i <= p; ++i){
if(g[i].size() > ans){
ans = g[i].size();
ak = i;
}
}
cout << ans << endl;
int len = g[ak].size();
priority_queue<int, vector<int>, greater<int> > q;
for(int i = 0; i < len; ++i) q.push(g[ak][i]);
while(q.size()){
cout << q.top() << ' ';
q.pop();
}
return 0;
}
完结撒花!!!
如果您认为博主写的好的话,记得点赞+收藏哦!