Skip to content

拓扑排序

课程表

  • 拓扑排序
ts
function canFinish(
  numCourses: number,
  prerequisites: number[][]
): boolean {
  // 出边和入边集合
  const outEdgeMap = new Map<number, Set<number>>();
  const inEdgeMap = new Map<number, Set<number>>();
  // b -> a
  for (const [a, b] of prerequisites) {
    if (!outEdgeMap.has(b)) {
      outEdgeMap.set(b, new Set());
    }
    outEdgeMap.get(b).add(a);
    if (!inEdgeMap.has(a)) {
      inEdgeMap.set(a, new Set());
    }
    inEdgeMap.get(a).add(b);
  }
  // 无入边节点的队列:不依赖其他课程,可以直接学习(删掉)
  const noInEdgeNodeQueue: number[] = [];
  for (let i = 0; i < numCourses; ++i) {
    if (!inEdgeMap.has(i)) {
      noInEdgeNodeQueue.push(i);
    }
  }
  while (noInEdgeNodeQueue.length) {
    // 选择一个没有入边的节点
    const u = noInEdgeNodeQueue.shift();
    // 删除其所有出边
    if (outEdgeMap.has(u)) {
      for (const v of outEdgeMap.get(u)) {
        const set = inEdgeMap.get(v);
        if (set) {
          set.delete(u);
          // 如果出现了新的无入边节点,加入队列
          if (set.size === 0) {
            inEdgeMap.delete(v);
            noInEdgeNodeQueue.push(v);
          }
        }
      }
      outEdgeMap.delete(u);
    }
  }
  // 最终所有节点都被删掉则说明能学完
  return inEdgeMap.size === 0;
}
  • DFS 检查图中是否有环
ts
function canFinish(
  numCourses: number,
  prerequisites: number[][]
): boolean {
  // 邻接表存储边
  const graph: number[][] = new Array(numCourses).fill(0).map(() => []);
  for (const [a, b] of prerequisites) {
    graph[b].push(a);
  }
  const vis: number[] = new Array(numCourses).fill(0);
  let valid = true;
  const dfs = (u: number) => {
    // 1 表示「搜索中」
    vis[u] = 1;
    for (const v of graph[u]) {
      // 0 表示「未搜索」
      if (vis[v] === 0) {
        // 继续搜索
        dfs(v);
        if (!valid) {
          return;
        }
      } else if (vis[v] === 1) {
        // 碰到重复节点,说明有环,不可能学完
        valid = false;
        return;
      }
    }
    // 2 表示「搜索完成」
    vis[u] = 2;
  };
  for (let i = 0; i < numCourses && valid; ++i) {
    if (!vis[i]) {
      dfs(i);
    }
  }
  return valid;
}