每天学一个算法--拓扑排序(Topological Sort)

📘 教案 13:拓扑排序(Topological Sort)


一、问题背景

在很多实际问题中,我们并不是单纯处理"数值",而是在处理依赖关系

例如:

  • 课程学习:必须先学 A 才能学 B
  • 构建系统:必须先编译库,再编译主程序
  • 任务调度:某些任务必须在其他任务完成后才能开始

这些问题可以抽象为一种结构:

有向图中的依赖关系


二、形式化定义

给定一个有向图 ( G = (V, E) ),其中:

  • ( V ):顶点(任务)
  • ( E ):有向边(依赖关系)

若存在边:

u→v\]\[ u \\to v \]\[u→v

表示:

必须先完成 ( u ),才能执行 ( v )


三、拓扑排序的定义

拓扑排序是:

对有向无环图(DAG)中的节点进行排序,使得所有依赖关系得到满足

换句话说:

如果存在边 ( u \to v ),那么在排序结果中:

u 一定出现在 v 之前\]\[ u \\text{ 一定出现在 } v \\text{ 之前} \]\[u 一定出现在 v 之前


四、关键前提:必须是 DAG


什么是 DAG?

DAG(Directed Acyclic Graph):

有向无环图


为什么必须无环?

如果存在:

text 复制代码
A → B → C → A

那么:

  • A 依赖 B
  • B 依赖 C
  • C 又依赖 A

👉 没有任何一个可以先执行

因此:

只要存在环,就不存在拓扑排序


五、核心思路

拓扑排序的核心是:

不断找到"没有依赖"的节点,然后删除它们


"没有依赖"是什么意思?

即:

入度=0\]\[ \\text{入度} = 0 \]\[入度=0


六、算法一:Kahn 算法(基于 BFS)


核心步骤

  1. 统计每个节点的入度

  2. 将所有入度为 0 的节点加入队列

  3. 依次处理队列:

    • 取出一个节点
    • 将其加入结果
    • 删除它的所有出边
    • 更新邻居节点的入度
  4. 如果有新的入度为 0 的节点,继续加入队列


代码结构

python 复制代码
from collections import deque

def topo_sort(graph):
    indegree = {v: 0 for v in graph}

    # 统计入度
    for u in graph:
        for v in graph[u]:
            indegree[v] += 1

    queue = deque([v for v in graph if indegree[v] == 0])
    result = []

    while queue:
        u = queue.popleft()
        result.append(u)

        for v in graph[u]:
            indegree[v] -= 1
            if indegree[v] == 0:
                queue.append(v)

    if len(result) != len(graph):
        return "存在环"

    return result

七、算法二:DFS 方法


思路

通过 DFS,在"回溯时"记录节点顺序。


关键点

  • 访问完所有子节点后,再加入结果
  • 最终将结果逆序

代码结构

python 复制代码
def topo_sort_dfs(graph):
    visited = set()
    result = []

    def dfs(node):
        if node in visited:
            return
        visited.add(node)

        for nei in graph[node]:
            dfs(nei)

        result.append(node)

    for node in graph:
        dfs(node)

    return result[::-1]

八、两种方法对比

方法 思路 特点
Kahn(BFS) 入度为0开始 易理解,能检测环
DFS 后序遍历 更偏递归结构

九、时间复杂度

O(V+E)\]\[ O(V + E) \]\[O(V+E)

  • 每个节点访问一次
  • 每条边处理一次

十、典型应用


1. 课程安排(经典题)

判断是否可以完成所有课程


2. 构建系统(编译顺序)


3. 任务调度


4. 数据处理依赖


十一、常见错误


  • 忘记判断是否有环(结果长度不等于节点数)
  • 入度更新错误
  • 图结构构建错误

十二、本质总结


拓扑排序解决的问题不是"排序大小",而是:

在一组存在依赖关系的元素中,找到一个合法执行顺序


更适合教学的一句话表达

拓扑排序是在有向无环图中,按照依赖关系对节点进行线性排序,使得所有前置条件都在使用之前被满足。

相关推荐
米罗篮24 分钟前
DSU并查集 & 拓展欧几里得-逆元
c++·经验分享·笔记·算法·青少年编程
橙淮25 分钟前
双指针法:高效算法解题的利器
算法
初心未改HD35 分钟前
深度学习之MLP与反向传播算法详解
人工智能·深度学习·算法
刀法如飞36 分钟前
【Go 字符串查找的 20 种实现方式,用不同思路解决问题】
人工智能·算法·go
技术小黑2 小时前
CNN算法实战系列03 | DenseNet121算法实战与解析
pytorch·深度学习·算法·cnn
wearegogog1233 小时前
三电平SVPWM逆变器仿真指南
单片机·算法
笨笨饿3 小时前
74_SysTick滴答定时器中断
c语言·开发语言·人工智能·单片机·嵌入式硬件·算法·学习方法
pkowner4 小时前
若依分页问题及解决方法
java·前端·算法
呃呃本4 小时前
算法题(栈)
算法