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


会编程的土豆 • 计科学习者
🔥 个人主页: 点击访问我的主页
📘 核心专栏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;
    }
};
相关推荐
求学的小高6 小时前
数据结构Day10(ASL、二分查找、分块查找)
数据结构·笔记·考研
我能坚持多久6 小时前
C++的Vector学习:从功能探索到底层实现
开发语言·c++·学习
她说彩礼65万6 小时前
C语言 动态内存管理
c语言·开发语言·算法
凤凰院凶涛QAQ6 小时前
《C++转java快速入手系列》类与对象篇
java·开发语言·c++
Irene19916 小时前
数据排序为什么默认升序
算法·排序
张健11564096486 小时前
std::ranges、std::views和懒加载
开发语言·c++
瞎折腾啥啊6 小时前
现代 CMake 目标系统
c++·cmake·cmakelists
盐焗鹌鹑蛋6 小时前
【C++】list类
c++
minji...6 小时前
Linux 网络套接字编程(六)TCP的通信是全双工的,自定义协议的定制,序列化和反序列化
linux·运维·服务器·网络·c++
.5486 小时前
DFS + BFS(深度优先搜索 & 广度优先搜索)
算法·深度优先·宽度优先