← 中文资源

简体中文版本

带环检测的拓扑排序

用 Kahn 算法排列依赖图,提供确定性顺序、未知依赖校验和清晰的环检测。

阅读英文原文

功能说明

topologicalSort 接收一个依赖图对象,键是任务或模块名,值是该节点依赖的节点数组,返回一个满足所有依赖先于使用者的顺序。它采用 Kahn 算法:先统计每个节点的入度,把入度为零的节点放入就绪队列,逐个取出并减少其后继节点的入度。适合构建系统、包安装顺序、数据库迁移和工作流编排。

实现会校验依赖项确实存在于图中,避免拼写错误被静默忽略。就绪队列按字典序插入,因此相同输入总是得到相同顺序,便于缓存和测试。如果处理结束后仍有节点未输出,说明存在环,错误信息会列出所有未解析节点。

关键代码

每处理一个节点,就遍历它的后继并递减入度;入度归零时按字典序插入就绪队列:

for (const next of outgoing.get(node) ?? []) {
  const remaining = (indegree.get(next) ?? 0) - 1;
  indegree.set(next, remaining);
  if (remaining !== 0) continue;

  let index = 0;
  while (index < ready.length && ready[index] < next) index += 1;
  ready.splice(index, 0, next);
}

使用注意

依赖数组按“当前节点依赖谁”解释,而不是“谁依赖当前节点”,方向弄反会得到相反的顺序。图对象只能表达字符串键,若要使用数字或其他类型,需要先映射为字符串。重复依赖会被去重,不会重复增加入度,因此同一依赖写两次不会造成错误,但也不会报错。

算法检测到环时会抛出错误,消息中的未解析节点可能包含环下游的节点,而不仅是环本身;需要精确环路径时应改用 DFS 着色法。空图返回空数组。排序结果只保证依赖关系,不保证同一层的节点有其他优先级;当前用字典序打破平局,若需要业务优先级应替换就绪队列的排序规则。

验证方式

示例中 app 依赖 db 和 cache,db、cache 都依赖 config,输出应为 config -> cache -> db -> app,体现依赖先行和字典序。再传入互相依赖的 a、b,应得到 cycle detected among: a, b;传入不存在的 missing,应得到 unknown dependency "missing" for "app"。可交换 cache 和 db 的声明顺序,确认输出仍保持稳定。