拓扑排序(Kahn算法)

拓扑排序(Kahn算法)

什么是拓扑排序

拓扑排序是对 有向无环图(DAG, Directed Acyclic Graph) 的顶点进行的一种线性排序,使得对于每一条有向边 (u, v),顶点 u 在排序结果中都出现在顶点 v 的前面。

核心前提:图必须是 DAG(无环),若图中存在环,则无法进行拓扑排序。

应用场景

拓扑排序主要用于处理依赖关系的场景,典型例子包括:

  • 课程安排:大学选课系统中,某些课程有先修课要求(如 "数据结构" 必须在 "算法分析" 之前学习),拓扑排序可给出合理的选课顺序。
  • 任务调度:项目管理中,多个任务存在依赖关系(如 "设计" 完成后才能 "开发"),拓扑排序可生成任务执行序列。
  • 编译顺序:编译器处理文件依赖时(如 .h 头文件被 .cpp 源文件依赖),拓扑排序可确定文件的编译顺序。

三、Kahn 算法原理与步骤

Kahn 算法是一种基于入度的贪心算法,核心思想是 "不断处理入度为 0 的顶点"。

原理

  • 入度定义:顶点的入度是指指向该顶点的有向边数量。
  • 贪心策略:每次选择入度为 0 的顶点(无前置依赖),将其加入拓扑序列,然后 "移除" 该顶点的所有出边(即减少邻接顶点的入度)。
    环检测:若最终拓扑序列的顶点数小于图的总顶点数,说明图中存在环。

步骤

  • 初始化:
    计算图中所有顶点的入度,存入 inDegree 数组。
    使用一个队列(或栈)存储所有入度为 0 的顶点。
  • 循环处理:
    从队列中取出一个顶点 u,加入拓扑序列。
    遍历 u 的所有邻接顶点 v,将 v 的入度减 1。
    若 v 的入度变为 0,将其加入队列。
  • 终止条件:
    队列为空时,检查拓扑序列长度:
    若长度等于顶点数:输出拓扑序列。
    若长度小于顶点数:图中存在环,无法拓扑排序。
java 复制代码
import java.util.*;

public class TopologicalSortKahn {

    // 拓扑排序函数,返回拓扑序列(若有环则返回空列表)
    public static List<Integer> topologicalSort(int numVertices, List<List<Integer>> adj) {
        int[] inDegree = new int[numVertices]; // 存储每个顶点的入度
        List<Integer> topoOrder = new ArrayList<>(); // 存储拓扑序列

        // 1. 计算所有顶点的入度
        for (int u = 0; u < numVertices; u++) {
            for (int v : adj.get(u)) {
                inDegree[v]++; // 边 u->v,v 的入度+1
            }
        }

        // 2. 初始化队列,加入所有入度为 0 的顶点
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < numVertices; i++) {
            if (inDegree[i] == 0) {
                queue.offer(i);
            }
        }

        // 3. 循环处理队列中的顶点
        while (!queue.isEmpty()) {
            int u = queue.poll(); // 取出入度为 0 的顶点 u
            topoOrder.add(u);     // 加入拓扑序列

            // 遍历 u 的所有邻接顶点 v
            for (int v : adj.get(u)) {
                inDegree[v]--; // 移除边 u->v,v 的入度-1
                if (inDegree[v] == 0) {
                    queue.offer(v); // 若 v 入度为 0,加入队列
                }
            }
        }

        // 4. 检测是否有环:若拓扑序列长度不等于顶点数,说明有环
        if (topoOrder.size() != numVertices) {
            return new ArrayList<>(); // 有环,返回空列表
        }

        return topoOrder;
    }

    // 测试主函数
    public static void main(String[] args) {
        // 示例:构建一个 DAG(顶点 0~5)
        int numVertices = 6;
        List<List<Integer>> adj = new ArrayList<>();
        for (int i = 0; i < numVertices; i++) {
            adj.add(new ArrayList<>());
        }

        // 添加边:u->v(u 是 v 的前置依赖)
        adj.get(0).add(1); // 0->1
        adj.get(0).add(2); // 0->2
        adj.get(1).add(3); // 1->3
        adj.get(2).add(3); // 2->3
        adj.get(3).add(4); // 3->4
        adj.get(3).add(5); // 3->5

        // 执行拓扑排序
        List<Integer> result = topologicalSort(numVertices, adj);

        // 输出结果
        if (result.isEmpty()) {
            System.out.println("图中存在环,无法拓扑排序!");
        } else {
            System.out.println("拓扑排序结果:" + result);
        }
    }
}
相关推荐
Shadow(⊙o⊙)19 小时前
日志与线程池
linux·c++·算法
冷小鱼19 小时前
AI Agent 的核心算法:多智能体协作(Multi-Agent Systems)
前端·人工智能·算法·multi-agent·多智能体协作·systems
QN1幻化引擎19 小时前
# DalinX V8 灵鉴 V2:12维意识评测框架 —— 从 Tononi IIT 到 Friston FEP 的理论统一
数据库·人工智能·算法·机器学习·数据挖掘·agi
牧以南歌〆20 小时前
数据结构<一>顺序表
c语言·数据结构·算法
Fairy要carry20 小时前
面试-训练显存占用估计
算法
触底反弹20 小时前
💥 暴力解 LeetCode 导致 OOM:10000 行数据撑爆 V8 堆内存的完整排查
javascript·算法·面试
cheems952720 小时前
[算法手记]二叉树中序,层序遍历,统计树高
算法
QXWZ_IA1 天前
1库1图1批是什么?千寻位置公安地图数据体系详解
科技·算法·能源·媒体·交通物流·政务
c238561 天前
Bug 猎手入门指南
c++·算法·bug