每天学一个算法--拓扑排序(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. 数据处理依赖


十一、常见错误


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

十二、本质总结


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

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


更适合教学的一句话表达

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

相关推荐
裕晟资质规划9 小时前
武器装备科研生产单位保密资质申请方法论:条件模型与流程拆解
人工智能·算法
en.en..11 小时前
C语言 标准输入 / 输出缓冲区
算法
吃好睡好便好12 小时前
查找函数的使用
学习·算法·matlab·生活·查找函数
学习星球12 小时前
单调栈——从“找下一个更大的“到柱状图中的最大矩形
数据库·c++·算法·leetcode·xcode
靠沿14 小时前
贪心算法专题(二)
算法·贪心算法
猎头南楼14 小时前
具身智能模型部署方向的能力要求与技术栈梳理
人工智能·深度学习·算法
啥都想学点的研究生15 小时前
一篇文章讲清楚:线性回归问题的求解——正规方程法
算法·机器学习·线性回归
青少儿编程课堂15 小时前
树状数组精讲:逆序对计数与离散化(附双语言解法)
算法·树状数组·离散化·信息学奥赛·逆序对
山峰哥16 小时前
数据库工程与查询优化案例深度复盘‌
数据库·sql·oracle·编辑器·深度优先·宽度优先
血小板要健康16 小时前
网格 dfs 与 FloodFill:从岛屿、区域到搜索路径
笔记·算法·leetcode·深度优先