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

用 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] == 0的i入队,哪怕 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 - 遇到邻居
v且state[v] == 1→ 立刻返回 true(发现环) - 注意:必须对每个未访问节点都调用 DFS,不能只从 0 开始——图可能不连通
indeg 数组越界、状态变量复用、节点编号与数组下标错位,都会让结果看起来“偶尔对偶尔错”。










