Kahn算法是C++中唯一稳定检测环、不爆栈、结果可复现的拓扑排序写法;需用vector indegree(n,0)和graph(n)初始化,边u→v对应indegree[v]++和graph[u].push_back(v),输入1-based边须转0-based,孤立节点也必须包含,BFS中每次--indegree[v]后立即判断是否为0,环检测唯一依据是result.size()!=n。

拓扑排序在 C++ 里没有 std::topological_sort,必须手写;Kahn 算法是唯一能稳定检测环、不爆栈、结果可复现的写法。
怎么初始化 indegree 和 graph 才不越界不漏点
节点编号必须和数组下标对齐——如果题目说“n 个节点,编号 0 到 n−1”,就老实用 vector<int> indegree(n, 0)</int> 和 vector<vector>> graph(n)</vector>。别用 map 或 unordered_map 存入度,除非节点编号稀疏(比如最大编号是 1e9,但实际只有 100 个点)。
-
indegree必须显式初始化为全 0:vector<int> indegree(n, 0)</int>安全;裸数组如int indegree[1000]必须配memset(indegree, 0, sizeof(indegree)) - 每条边
u → v只做indegree[v]++和graph[u].push_back(v);写反成graph[v].push_back(u)就彻底失效 - 输入边是 1-based(如
[1,2])?读入后立刻转成u-1,v-1,否则后续所有下标访问都越界 - 孤立节点(既无入边也无出边)也要被覆盖:它满足
indegree[i] == 0,会在初始化阶段进队;漏掉这个i,结果长度一定小于n
为什么 BFS 过程中必须 --indegree[v] 后立刻判断是否为 0
不是“减完再统一扫一遍”,而是“每减一次,马上看这次减有没有把它推到 0”。这是 Kahn 算法能逐层推进、不漏节点的核心机制。
- 错误写法:
indegree[v]--;然后跳过判断,等下一轮 BFS 再检查——节点v就永远卡在indegree[v] == 1状态 - 正确写法:
if (--indegree[v] == 0) q.push(v);,减和判是原子操作 - 如果图含重边(两条相同的
u→v),indegree[v]会被加两次,那单次--就不够清零;建图时需去重,或改用set预处理边 - 别加额外条件,比如
&& !visited[v]——indegree[v] == 0本身就是唯一入队依据,加了反而容易错
怎么判断图中存在环——别信队列空了
环检测不靠异常、不靠 DFS 回溯标记,只看最终结果长度:result.size() != n 就是铁证有环。
立即学习“C++免费学习笔记(深入)”;
- 常见现象:queue 很快变空,但
result只有前几个节点——这不是性能问题,是明确环信号 - 不要检查
indegree数组是否全为 0:有些节点你根本没访问过,它的值还是初始 0 或未定义值,不能反映真实状态 - 也不要用
q.empty()当结束条件;BFS 结束后必须比对result.size()和总节点数n - 若要求返回失败原因,可额外扫描:遍历所有
i,收集indegree[i] > 0的节点,它们就是环上或环后可达的节点
为什么不用 DFS 版本而坚持用 queue + indegree
DFS 拓扑排序容易栈溢出、状态标记易错(比如漏清 recStack)、环检测逻辑脆弱;Kahn 算法用 queue 是为了保证 FIFO 语义,模拟“谁先没依赖谁先排”的真实调度逻辑。
- 换成
stack就退化成 DFS 模拟,失去天然环检测能力 - 用
priority_queue只在需要字典序最小拓扑序时才加,否则徒增O(log n)常数开销 -
queue<int></int>足够,别用list或自定义容器替代,缓存不友好且无必要 - 函数返回空
vector<int></int>就代表有环——这是 Kahn 最实用的副产物,不需要额外判环逻辑
真正容易被忽略的是:节点总数 n 必须严格等于你认定的“全部可能节点”个数。哪怕只漏初始化一个孤立节点的 indegree,整个环检测就失效——写完第一件事,盯一眼 vector<int> indegree(n, 0)</int> 里的 n 是不是真对。


















