用indegree数组而不用map是因为节点编号通常连续,vector访问O(1)更快更安全;map引入log n开销且默认构造会干扰拓扑序判断。

为什么用 indegree 数组而不用 map<int int></int> 存入度?
图的节点编号通常是连续整数(如 0 到 n−1),此时用 vector<int></int> 存 indegree 更快、更安全。用 map 会引入 log n 查找开销,且容易因键不存在导致默认构造(比如访问未声明的节点时自动插入 indegree[100] = 0),干扰拓扑序判断。若节点 ID 是稀疏或非数字(如字符串),才考虑 unordered_map,但必须配合显式初始化——所有出现过的节点都要提前设 indegree[node] = 0,否则后续 indegree[v]-- 可能创建新条目,让队列误推“零入度但实际未声明”的节点。
queue<int></int> 里 push 的到底是什么?
是当前入度为 0 的节点编号,不是边、不是权重、也不是邻接表索引。它代表“此刻可安全执行”的任务或模块。BFS 过程中每次 pop 一个节点 u,就遍历其所有邻接点 v,对每个 v 执行 indegree[v]--;一旦 indegree[v] 变成 0,立刻 push(v)。注意:不能在遍历邻接表时直接检查 indegree[v] == 0 再 push——因为减完才可能为 0,必须先减再判。
常见错误包括:
- 把
adj[u].size()当作可入队节点数,错 - 用
stack替代queue,得到的是逆拓扑序(有时可用,但不符合 Kahn 原意) - 在
for (auto v : adj[u])循环外做push,漏掉刚变零的节点
如何判断图含环?
拓扑排序完成后,结果序列长度是否等于节点总数 n。不是靠“队列空了但还有节点没访问”这种模糊描述——必须严格比对:result.size() != n 即存在环。这是因为环内所有节点入度永远无法减到 0,它们根本不会进队,也不会被加入 result。注意:初始化时要确保 indegree 对每个节点都覆盖(哪怕为 0),否则未初始化的节点会被忽略,导致误判为有环。
立即学习“C++免费学习笔记(深入)”;
典型误操作:
- 只对有入边的节点设置
indegree,漏掉“纯起点”节点(入度本应为 0) - 用
if (!q.empty())判断是否有环,错——队列空只是当前无零入度节点,不代表结束 - 在 BFS 中提前 return false,没等整个循环结束就中断
邻接表用 vector<vector>></vector> 还是 vector<list>></list>?
日常实现一律用 vector<vector>></vector>。插入和遍历局部性好,cache 友好;list 的指针跳转反而拖慢。除非你频繁在中间删边(Kahn 不需要删边,只读),否则没有优势。另外注意:建图时每加一条边 u → v,只做两件事:adj[u].push_back(v) 和 indegree[v]++。别反着写,也别漏掉 indegree[v]++——这是整个算法的起点,漏一次,v 就永远卡在非零入度状态。
示例片段(关键逻辑):
vector<vector<int>> adj(n);
vector<int> indegree(n, 0);
for (auto& e : edges) {
int u = e[0], v = e[1];
adj[u].push_back(v);
indegree[v]++; // ← 这行漏不得
}
拓扑序本身不唯一,但 Kahn 算法给出的一定是某个合法序;真正容易被忽略的是:入度数组必须与节点 ID 空间严格对齐,且初始化不能依赖“默认值”,尤其当节点编号不从 0 开始或存在空洞时。


















