C++中的塔尖算法(Tarjan算法)详解——目录
C++中的塔尖算法(Tarjan算法)详解
一、什么是Tarjan算法?
Tarjan算法是一种由Robert Tarjan提出的高效图论算法,主要用于求解有向图的强连通分量(Strongly Connected Components, SCC)。该算法基于深度优先搜索(DFS),能够在线性时间内完成这一任务,时间复杂度为 O ( n + m ) O(n + m) O(n+m),其中 n n n是顶点数, m m m是边数。其核心思想是通过维护一个栈和两个关键数组(dfn
和low
)来识别图中的所有强连通分量。
二、算法原理与实现步骤
1. 核心概念
dfn[u]
:记录节点 u u u被访问的顺序编号(时间戳)。low[u]
:表示从节点 u u u出发能追溯到的最早栈中节点的时间戳。- 栈结构:用于存储当前路径上的节点,帮助判断哪些节点属于同一个强连通分量。
2. 主要逻辑
以下是算法的关键步骤:
- 初始化:对每个未访问过的节点调用
tarjan(u)
函数。- 设置
dfn[u] = low[u] = ++index
,并将节点压入栈中。
- 设置
- 遍历邻接点:对于每个邻接点 v v v:
- 如果 v v v未被访问过,则递归调用
tarjan(v)
,然后更新low[u] = min(low[u], low[v])
。 - 如果 v v v已在栈中(即处于当前搜索路径上),则用
dfn[v]
更新low[u]
。
- 如果 v v v未被访问过,则递归调用
- 判断根节点:当
dfn[u] == low[u]
时,说明找到了一个强连通分量的根节点。此时将栈中从该节点开始的所有元素弹出,这些节点构成一个完整的SCC。
3. C++代码示例
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
const int MAXN = 1e5 + 5; // 根据实际需求调整大小
vector<int> g[MAXN]; // 邻接表存图
int dfn[MAXN], low[MAXN], color[MAXN]; // dfn/low数组及所属颜色标记
bool inStack[MAXN]; // 标记是否在栈内
stack<int> stk; // 辅助栈
int timestamp = 0; // 全局时间戳
int sccCount = 0; // 强连通分量计数器
void tarjan(int u) {
dfn[u] = low[u] = ++timestamp; // 初始化时间戳
stk.push(u);
inStack[u] = true;
for (auto v : g[u]) {
if (!dfn[v]) { // 未访问过
tarjan(v);
low[u] = min(low[u], low[v]); // 更新low值
} else if (inStack[v]) { // v在栈中,属于当前路径
low[u] = min(low[u], dfn[v]); // 用dfn更新low
}
}
// 如果u是某个SCC的根节点
if (dfn[u] == low[u]) {
++sccCount; // 新增一个SCC
while (true) {
int x = stk.top();
stk.pop();
inStack[x] = false; // 移出栈并标记
color[x] = sccCount; // 染色分类
if (x == u) break; // 直到弹出u为止
}
}
}
int main() {
int n, m;
cin >> n >> m; // 输入点数和边数
for (int i = 0; i < m; ++i) {
int a, b;
cin >> a >> b; // 添加有向边a→b
g[a].push_back(b);
}
// 对所有未访问节点执行Tarjan算法
for (int i = 1; i <= n; ++i) {
if (!dfn[i]) {
tarjan(i);
}
}
// 输出结果:每个节点所属的SCC编号
for (int i = 1; i <= n; ++i) {
cout << "点" << i << "属于第" << color[i] << "个SCC" << endl;
}
return 0;
}
三、应用场景与扩展
1. 典型应用
- 缩点优化:将每个SCC视为单个超级节点,构建新的有向无环图(DAG),便于后续处理如拓扑排序、最长路径等问题。
- 双连通分量检测:稍作修改后可用于寻找无向图中的桥和割点。
- 竞赛题目:许多图论问题通过缩点转化为更简单的形式,例如动态规划或贪心策略的应用。
2. 注意事项
- 多次调用的处理:确保每次DFS前清空相关状态变量(如
vis
数组)。 - 内存管理:对于大规模数据,建议使用动态分配而非静态全局数组。
- 边界条件:注意自环边和重复边的处理方式。
四、为什么选择Tarjan算法?
相较于其他方法(如Kosaraju算法),Tarjan算法的优势在于:
- 单次DFS完成所有工作,无需额外反向建图;
- 空间效率高,仅需常数额外空间维护栈和标记数组;
- 易于扩展,支持多种变体以适应不同场景需求。
五、总结
Tarjan算法作为图论中的经典算法之一,其高效性和灵活性使其成为解决强连通分量相关问题的首选工具。通过深入理解其原理并结合实践编码,你可以更轻松地应对复杂的图结构分析任务。无论是竞赛编程还是实际工程应用,掌握这一算法都将显著提升你的解题能力!