tarjan求割点需区分根与非根:根节点是割点当且仅当有≥2棵子树;非根节点u是割点当且仅当存在子节点v满足low[v] >= dfn[u];割边判定统一为low[v] > dfn[u],无需区分根节点,且不依赖栈。

Tarjan算法求割点的核心判断条件
割点判定不是直接看 low[v] >= dfn[u] 就完事——必须区分 u 是否为根节点。根节点是特例:只有当它有两个及以上不相交的子树(即搜索树中至少两个子节点的 dfn 未被更早访问过),才是割点;非根节点才用 low[v] >= dfn[u] 判断。
常见错误是把根节点也套用同一条件,导致漏判或误判。实现时需单独记录每个节点的子树数量(在 DFS 进入子节点前计数),回溯后检查。
- 对非根节点
u:若存在子节点v满足low[v] >= dfn[u],则u是割点 - 对根节点
root:统计其在 DFS 树中的直连子节点个数(注意不是邻接点个数),≥2 即为割点 -
dfn和low数组必须初始化为 0,且用全局时间戳递增,不能复用强连通分量里的栈操作逻辑
Tarjan算法求割边(桥)的唯一判断式
割边比割点简单:一条无向边 (u, v) 是桥,当且仅当 low[v] > dfn[u](注意是严格大于)。这个条件和图是否连通、u 是否为根都无关,统一适用。
容易踩的坑是写成 >=——那样会把“父节点到子节点但子树能绕回父节点上方”的边也误判为桥。另外,无向图建邻接表时必须避免自环和重边干扰 dfn 更新,推荐用 pair<int int></int> 记录边端点,在 DFS 中跳过反向边(即跳过 v == parent 的情况)。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 建图时每条无向边存两次,但 DFS 遍历时用
parent参数跳过回头边 - 判断桥只在首次访问
v后、回溯前进行:if (low[v] > dfn[u]) ans_bridges.push_back({u, v}); - 不要在缩点或 SCC 处理流程里混用
stack和in_stack[],割点/割边不需要维护栈
为什么不能直接复用强连通分量的 Tarjan 代码
强连通分量 Tarjan(有向图)和割点/割边 Tarjan(无向图)虽然共用 dfn/low 思想,但底层逻辑不同:前者依赖栈维护当前路径上的节点,后者完全不需要栈,也不需要出栈操作;前者用 low[u] = min(low[u], dfn[v]) 处理横叉边,后者只在 v 未访问时更新 low,且不处理已访问但非父节点的点(即不处理“横叉边”,因为无向图没有真正意义的横叉边)。
- 强连通分量版本的
low[u] = min(low[u], low[v])在割点/桥中是错的——应为low[u] = min(low[u], dfn[v]) - 强连通分量中常写
if (in_stack[v]) low[u] = min(low[u], dfn[v]),这在无向图中无意义,且易引入 bug - 输入图类型必须明确:给的是无向图才能求割点/割边;强行对有向图跑此逻辑结果无定义
实际编码时最容易忽略的细节
很多人卡在输出重复或漏边,根本原因是没处理好无向图的双向边表示与 DFS 访问控制。比如用邻接矩阵或邻接表时,若未标记“已访问边”或未传父节点,会导致同一条边被正反各走一次,dfn 被反复覆盖,low 失真。
- DFS 函数签名建议为
dfs(int u, int parent),进入子节点前检查v != parent - 使用
vector<vector>></vector>存图时,确保加边是g[u].push_back(v); g[v].push_back(u); - 时间戳从 1 开始(
timer = 1),避免用 0 初始化后与未访问状态混淆 - 割点数组用
is_cut[u] = true标记即可,无需去重——同一个点不会被多次标记
复杂点在于父子关系必须显式传递,而不能靠栈顶或访问顺序推断;一旦忽略 parent,整个 low 值就不可信。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










