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


会编程的土豆 • 计科学习者
🔥 个人主页: 点击访问我的主页
📘 核心专栏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;
    }
};
相关推荐
今天又是充满希望的一天2 小时前
C++分布式系统知识
开发语言·c++
zth4130213 小时前
SegmentSplay‘s Super STL(v2.2)
开发语言·c++·算法
数据知道3 小时前
claw-code 源码详细分析:Bootstrap Graph——启动阶段图式化之后,排障与扩展为什么会变简单?
前端·算法·ai·bootstrap·claude code·claw code
Kel3 小时前
从Prompt到Response:大模型推理端到端核心链路深度拆解
人工智能·算法·架构
Felven3 小时前
D. Matryoshkas
算法
17(无规则自律)3 小时前
DFS连通域统计:岛屿数量问题及其变形
c++·算法·深度优先
笨笨饿3 小时前
34_数据结构_栈
c语言·开发语言·数据结构·人工智能·嵌入式硬件·算法
im_AMBER3 小时前
Leetcode 152 被围绕的区域 | 岛屿数量
数据结构·算法·leetcode·深度优先·广度优先·图搜索算法
吕司4 小时前
LeetCode Hot Code——最大子数组和
数据结构·算法·leetcode