C++怎么实现拓扑排序检测环_C++DAG判断方法【图论】

3次阅读

kahn算法用std::queue和入度数组实现拓扑排序:建图时记录每条边u→v并统计indeg[v]++,初始化将所有indeg[i]==0的节点入队,每次出队一个点并对其邻接点减入度,减至0则入队;最终若加入拓扑序列的节点数cnt≠总节点数n,则存在环。

C++怎么实现拓扑排序检测环_C++DAG判断方法【图论】

std::vector<:vector>></:vector> 建图后怎么跑拓扑排序

拓扑排序本质是判断 DAG(有向无环图)是否成立,核心动作就是「删入度为 0 的点,更新邻居入度,重复直到删完或卡住」。c++ 里最常用的是 Kahn 算法,依赖 std::queue 和入度数组。

常见错误现象:queue 为空但还有节点没访问到 → 说明存在环;或者漏初始化入度数组,导致未定义行为。

  • 建图时用 adj[u].push_back(v) 表示边 u → v,同时对每个 v 执行 indeg[v]++
  • indeg 数组大小必须覆盖所有可能的节点编号(比如节点是 1~n,数组就要开 n+1,下标 0 不用也行)
  • 初始把所有 indeg[i] == 0i 入队,哪怕 i 是孤立点(入度出度都为 0)也要算进拓扑序列
  • 每次从队列弹出一个点,对其所有邻接点执行 indeg[v]--,减到 0 就立刻入队

检测环时为什么不能只看 queue.empty()

很多人写完 Kahn 就直接检查最后 queue 是否为空,这是错的——queue 只反映“当前可删的点”,不是“是否删完了”。真正判断环的依据是:最终加入拓扑序列的节点数是否等于总节点数。

使用场景:你传入了 n 个节点,但图里实际只出现过其中一部分编号(比如只有 0、2、4),这时要小心「未出现的节点是否该计入总数」。通常约定:输入明确给出节点范围 [0, n) 或 [1, n],就以 n 为准。

立即学习C++免费学习笔记(深入)”;

  • 维护一个计数器 cnt,每弹出一个点就 cnt++
  • 循环结束后,cnt != n 就代表有环
  • 别用 queue.size() == 0 && cnt 当判断条件——队列空是常态,不意味着结束

std::stack 替换 std::queue 会影响环检测结果吗

不影响。Kahn 算法中用还是队列,只改变拓扑序的输出顺序(DFS-like vs BFS-like),不改变「能否遍历全部节点」这个事实。环的存在性是图的固有属性,和遍历容器无关。

性能影响:栈在缓存局部性上略好,但差别微乎其微;兼容性无差异,都是标准容器。

  • 如果用 std::stack,记得用 s.top()/s.pop(),别误用 front()
  • 不要以为“栈=DFS”就自动支持环检测回溯——Kahn 本身不含递归或回溯逻辑
  • 某些 OJ 题目要求字典序最小的拓扑序,这时得用 std::priority_queue,但那是另一回事,和环检测无关

用 DFS 实现环检测时 visited 要分三种状态

DFS 版本不生成拓扑序,只判环,但更贴近直觉:遇到正在递归中的节点,就说明成环。关键在于 visited 不能只是 bool,必须区分「未访问」「访问中」「已访问完」。

错误现象:仅用 bool visited[],会把反向边(back edge)误判为环;或者漏掉跨连通分量的环(其实不会,但状态不清会导致逻辑混乱)。

  • 推荐用 vector<int> state(n, 0)</int>:0 = 未访问,1 = 访问中(在当前 DFS 栈上),2 = 已完成
  • 进入 DFS 前设 state[u] = 1,返回前设 state[u] = 2
  • 遇到邻居 vstate[v] == 1 → 立刻返回 true(发现环)
  • 注意:必须对每个未访问节点都调用 DFS,不能只从 0 开始——图可能不连通

拓扑排序检测环这件事,难点不在代码长短,而在于对「入度清零」和「DFS 栈帧活性」这两个抽象概念的准确映射。稍不注意,indeg 数组越界、状态变量复用、节点编号与数组下标错位,都会让结果看起来“偶尔对偶尔错”。

text=ZqhQzanResources