首先要明白dfs树的概念
dfs生成树是指在一个图中,选取任意一个节点,依据dfs算法遍历节点得到的一棵生成树。由上述定义可知,该树不唯一。
先把dfs生成树的代码展示如下(关于dfs生成树的五种边,见):
#include<bits/stdc++.h>
using namespace std;
vector<vector<int>> adj; // 邻接链表表示图
vector<bool> visited; // 访问过
vector<int> parent; // 父节点
vector<pair<int, int>> tree_edges;
vector<pair<int, int>> back_edges; // 对于无向图,dfs生成树只需要记录树边和回边
void dfs_build_tree(int u, int fa) {
visited[u] = true;
parent[u] = fa; // 记录父节点
for (int v : adj[u]) {
if (!visited[v]) {
tree_edges.push_back({u, v}); // 记录树边
dfs_build_tree(v, u); // 递归
} else if (v != fa) {
back_edges.push_back({u, v}); // 记录回边
}
}
}
// 只生成树,不进行分析
然后是tarjan_scc算法,这个算法在构建一棵dfs生成树的同时还求出了最大连通分量,为此要维护以下几个参数:
1.int dfn[MAXN],用于记录构建过程中节点发现时间
2.int low[MAXN],用于记录最早能回溯到的祖先的时间戳
3.计数器int timestamp
4.tarjan栈 stack
5.入栈标志 bool inStack[MAXN]
6.int scc_id[MAXN] 节点的scc编号,也就是说这个节点属于哪个scc
7.int scc_cnt scc计数器
因而在dfs生成树的基础上修改,有以下代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN;
int dfn[MAXN], low[MAXN];
int timestamp = 0;
stack<int> stk; // tarjan栈
bool inStack[MAXN]; // 节点是否在栈中
int scc_id[MAXN]; // 节点所属的scc编号
int scc_cnt = 0; // scc计数器
vector<vector<int>> adj;
vector<vector<int>> new_adj;
void tarjan(int u){
dfn[u] = low[u] = ++timestamp; // 依据时间戳初始化dfn和low的值
stk.push(u);
inStack[u] = true;
for(int v : adj[u]){
if(!dfn[v]){ // 相当于先前的!visited[v]
tarjan(v);
low[u] = min(low[u], low[v]); // 有向图中找前后两项最小的时间戳
// 类似的这里也可以加上push_back入树边vector
} else if(inStack[v]){ // v在栈中,说明v是u的祖先,(u,v)是回边
low[u] = min(low[u], dfn[v]);
}
}
// 在遍历完当下节点后,判断是否遍历完当前强连通分量
if(dfn[u] == low[u]){
scc_cnt++;
int v;
do{
v = stk.top(); stk.pop(); inStack[v] = false; // 取出栈顶
scc_id[v] = scc_cnt; // 标记归属
}while(v != u);
}
}
注意:因为DFS只能向下访问到连通的部分,一次tarjan不一定能完成全部节点的处理,在主循环调用的时候,应当这样写:
for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i);
通过tarjan_scc得到强连通分量,我们可以对图进行缩边/缩点的操作,去掉原图里的环,得到有向无环图。
浙公网安备 33010602011771号