tarjan
用于求强连通分量
细节内容
- low[] 数组改小是为了避免提前满足自己是自己的父节点而输出连通分量(因为一个强连通分量中,它的叶节点自身就是一个“强连通分量”,而真正的强连通分量是极大的)
- 程序中两次更新low,有环时改小不需要比较大小,一定是先访问的比较小
- 有环时改小先于回溯时改小,因为有环时改小了,才能在回溯的过程中,通过比较改小之前访问过的节点。
- 栈用来记录已经被访问过的值,
Dfn 标记是否访问过,避免重复访问
S 标记访问过,但是还在寻找整个强连通分量的点,避免提前输出不完整的强连通分量,当找到整个强连通分量的起始点时,沿着这个顶点输出所有的该强连通分量内的值,并弹栈,则对于这一强连通块的访问彻底结束。
模板
中间各种注释为代码解释
注释中的输出是递归树
输入样例
/*
9 13
1 2
1 7
2 3
2 5
2 9
3 4
4 2
5 6
5 7
5 8
6 5
8 6
9 1
*/
#include<iostream>
using namespace std;
int A[100][100]; // 邻接点
int Dfn[100], num; // 记录邻接点的访问次序
int Low[100]; // 动态改小点的访问次序
int Stack[100], top; // 顶点入栈,强连通分量出栈
bool out[100]; // 出栈标记
void DFS(int u) {
int v;
Dfn[u] = Low[u] = ++num; // 记录u的访问次序
Stack[++top] = u; // 入栈
// printf("调用DFS(%d) D[%d]=L[%d]=%d ""S[%d]=%d\n", u, u, u, num, top, u); // 输出查看调用过程
for(int i = 1; i <= A[u][0]; i++) {
v = A[u][i]; // 取出u的邻接点
if(Dfn[v] == 0) { // 若没访问过
DFS(v);
Low[u] = min(Low[u], Low[v]); // 回溯到点u时,改小Low[u],可能有环出现,下面子节点的Low[v]已经更小了
// printf("回溯 %d->%d L[%d]=%d\n", v, u, u, Low[u]); // 跟踪输出
}// v 已访问且在栈中,说明u,v已构成环
else if(!out[v]){ // 由于栈中压在下面的元素不能立刻弹出,所以用out来标记弹出栈
Low[u] = Low[v]; // 有环时改小low,由于时从u访问到v,且v已经被访问过了,说明v先出现,次序肯定比u小,直接更新,不用比较
// printf("有环 %d->%d L[%d]=%d\n", u, v, u, Low[u]);
}
}
// printf("判断 if(%d==%d)?\n", Dfn[u], Low[v]); // 跟踪
// 向上层回溯前,判断根
if(Dfn[u] == Low[u]) { // u是当前分量的根
printf("分量 ");
while(Stack[top]!=u){
printf("%d ", Stack[top]); // 输出强连通分量成员
out[Stack[top--]] = 1;
} // u之后访问的点均出栈
printf("%d ", u); // u出栈
out[Stack[top--]] = 1; // 出栈标记
// printf("栈顶 S[%d]=%d\n\n", top, Stack[top]); //跟踪
}
}
int main() {
int u, v, n, m; // 点数, 边数
scanf("%d%d", &n, &m);
for(int i = 0; i < m; i++) {
scanf("%d%d", &u, &v);
A[u][++A[u][0]] = v; // 邻接点 ,(邻接表存图)
}
for(int i = 1; i <= n; i++) { // 图可能为森林,所以要从每个都都出发去找才能把所有强连通分量找全
if(Dfn[i] == 0) DFS(i);
}
return 0;
}
输出结果