【数据结构与算法】 拓扑排序


会编程的土豆 • 计科学习者
🔥 个人主页: 点击访问我的主页
📘 核心专栏1: 数据结构与算法
📘 核心专栏2: LeetCode Hot 100
✨ 面包会有的,牛奶会有的,一切都会有的!

拓扑排序:一篇就够了!从入门到精通

你还在为课程安排、任务调度发愁吗?拓扑排序就是你的救星!

什么是拓扑排序?

想象一下,你要安排一学期的课程,有些课必须先修其他课才能上:

  • 微积分 → 线性代数 → 机器学习

拓扑排序就是:对DAG(有向无环图)的所有顶点进行线性排序,使得对于每条边 u→v,u 都出现在 v 之前。

核心关键词

  • DAG:有向无环图(Directed Acyclic Graph)
  • 线性排序:排成一列
  • 依赖关系:谁必须在谁前面

为什么需要拓扑排序?

应用场景 具体问题 拓扑排序的作用
🎓 课程安排 先修课依赖 排出合理的学习顺序
🔧 编译构建 文件依赖 确定编译顺序
📦 包管理 依赖关系 解决安装顺序
🏗️ 项目管理 任务依赖 制定项目计划

🚀 核心算法一:Kahn算法(入度法)

核心思想

不断删除入度为0的节点,就像剥洋葱一样层层剥开!

算法步骤

python 复制代码
1. 计算每个节点的入度
2. 将入度为0的节点加入队列
3. 循环处理:
   - 取出队首节点,加入结果
   - 将它指向的节点入度减1
   - 如果入度变为0,加入队列
4. 判断结果数量 == 节点总数
c++ 复制代码
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

vector<int> topologicalSortKahn(vector<vector<int>>& graph, int n) {
    // 1. 计算入度
    vector<int> inDegree(n, 0);
    for (int u = 0; u < n; u++) {
        for (int v : graph[u]) {
            inDegree[v]++;
        }
    }
    
    // 2. 入度为0的节点入队
    queue<int> q;
    for (int i = 0; i < n; i++) {
        if (inDegree[i] == 0) {
            q.push(i);
        }
    }
    
    // 3. 核心处理
    vector<int> result;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        result.push_back(u);
        
        for (int v : graph[u]) {
            inDegree[v]--;
            if (inDegree[v] == 0) {
                q.push(v);
            }
        }
    }
    
    // 4. 检查是否有环
    if (result.size() != n) {
        return {};  // 存在环
    }
    return result;
}

执行过程图解

java 复制代码
初始图:
    0 ──→ 1 ──→ 3
    │
    └──→ 2 ──→ 3

步骤1: 入度[0]=0 → 队列[0]
步骤2: 处理0,解锁1和2 → 队列[1,2]
步骤3: 处理1,解锁3(入度3→1) → 队列[2]
步骤4: 处理2,解锁3(入度3→0) → 队列[3]
步骤5: 处理3 → 完成

结果: [0, 1, 2, 3] ✓

核心算法二:DFS算法(深度优先)

核心思想

沿着依赖链走到尽头,回溯时记录节点

算法步骤

java 复制代码
1. 对每个未访问节点执行DFS
2. DFS过程中:
   - 标记为"访问中"
   - 递归访问所有邻居
   - 发现"访问中"节点 → 存在环
   - 回溯时压入栈
3. 栈反转得到拓扑序

完整代码实现

java 复制代码
class TopologicalSortDFS {
private:
    vector<vector<int>> graph;
    vector<int> visited;  // 0=未访问, 1=访问中, 2=已完成
    stack<int> stk;
    bool hasCycle = false;
    
    void dfs(int u) {
        visited[u] = 1;  // 标记访问中
        
        for (int v : graph[u]) {
            if (visited[v] == 1) {  // 发现环!
                hasCycle = true;
                return;
            }
            if (visited[v] == 0) {
                dfs(v);
                if (hasCycle) return;
            }
        }
        
        visited[u] = 2;  // 标记完成
        stk.push(u);     // 回溯时记录
    }
    
public:
    vector<int> sort(vector<vector<int>>& g, int n) {
        graph = g;
        visited.assign(n, 0);
        
        for (int i = 0; i < n; i++) {
            if (visited[i] == 0) {
                dfs(i);
                if (hasCycle) return {};
            }
        }
        
        vector<int> result;
        while (!stk.empty()) {
            result.push_back(stk.top());
            stk.pop();
        }
        return result;
    }
};
相关推荐
東隅已逝,桑榆非晚1 小时前
算法2. 双指针法-复写零
c++·笔记·学习·算法
布莱克6051 小时前
C++ 中堆和栈的区别详解
开发语言·c++·栈·堆
云析赢指标公式网42 小时前
文华财经指标源码笔记:WINNER筹码函数、控盘变量与MACD变体的调试记录
java·笔记·算法
誰能久伴不乏2 小时前
用 UDP 做一条“可靠“的控制通道
c++·qt·网络协议·udp
十五年专注C++开发2 小时前
FlatBuffers: 一个高效的零拷贝二进制序列化库
c++·序列化·反序列化·flatbuffers
xys_6783 小时前
深挖C++:C++入门基础
开发语言·c++
高冷小鱼殿下3 小时前
C 语言指针与输入输出
c++
CC数分3 小时前
统计学基础|先把问题问对:数据分析的六大类型
大数据·人工智能·算法
地平线开发者4 小时前
CLIP 的训练机制:从对比学习到图文对齐
深度学习·算法·自动驾驶
路溪非溪4 小时前
C++标准库之智能指针
linux·开发语言·c++