跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

BFS 实现拓扑排序:原理与 LeetCode 实战

拓扑排序用于确定有向无环图中的顶点线性顺序,常用于任务调度与依赖管理。通过 BFS 结合入度表的方法,详解如何检测图中是否存在环以及如何生成有效排序序列。结合课程表与外星词典案例,展示了从建图到队列处理的完整流程,确保在 O(V+E) 时间复杂度内解决问题。

涅槃凤凰发布于 2026/3/15更新于 2026/7/440 浏览
BFS 实现拓扑排序:原理与 LeetCode 实战

一、拓扑排序基础

1.1 有向无环图(DAG)

有向无环图是指一个没有回路的有向图。简单来说,如果从某个顶点出发无法经过若干条边回到该点,那么这个图就是 DAG。

文章配图

1.2 AOV 网:顶点活动图

在 DAG 的基础上,我们用顶点表示活动,用边表示活动执行的先后顺序关系。

文章配图

1.3 什么是拓扑排序

拓扑排序的核心目标是找到做事的先后顺序。具体做法是:每次找出入度为 0 的点,将其加入结果序列,并删除该点相连的边,重复此过程直到所有点都被处理或发现无法继续。

1.4 BFS 实现思路

借助队列来实现 BFS 遍历,流程如下:

  1. 初始化:将所有入度为 0 的点加入队列。
  2. 循环处理:当队列不为空时,取出队头元素加入最终结果。
  3. 更新依赖:删除与该点相连的边,即减少相邻点的入度。
  4. 入队判断:若某相邻点入度变为 0,则将其加入队列。

二、经典案例解析

2.1 课程表 I(检测可行性)

题目核心:给定课程数量和先修条件,判断是否能完成所有课程。

解题逻辑: 我们使用邻接表 List<List<Integer>> 来表示图,下标对应课程 ID,数组内容指向后续课程。遍历先修条件数组构建图的同时统计每个课程的入度。

这里有个细节要注意:如果没有任何先修条件,直接返回 true;另外,每个点只能入队一次,避免重复计算。

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        int m = prerequisites.length;
        if (m == 0) return true;
        
        // 建图:邻接表
        List<List<Integer>> edges = new <>();
         (   ; i < numCourses; i++) {
            edges.add( <>());
        }
        
        Queue<Integer> queue =  <>();
        [] inDegree =  [numCourses];
        [] visited =  [numCourses];
        
        
         (   ; i < m; i++) {
               prerequisites[i][];
               prerequisites[i][];
            edges.get(pre).add(course);
            inDegree[course]++;
        }
        
        
         (   ; i < numCourses; i++) {
             (inDegree[i] == ) {
                queue.add(i);
                visited[i] = ;
            }
        }
        
         (queue.isEmpty())  ;
        
        
         (!queue.isEmpty()) {
               queue.poll();
            
             ( neighbor : edges.get(tmp)) {
                inDegree[neighbor]--;
                
                 (inDegree[neighbor] ==  && !visited[neighbor]) {
                    queue.add(neighbor);
                    visited[neighbor] = ;
                }
            }
        }
        
        
         (   ; i < numCourses; i++) {
             (inDegree[i] != )  ;
        }
         ;
    }
}
ArrayList
for
int
i
=
0
new
ArrayList
new
LinkedList
int
new
int
boolean
new
boolean
// 统计入度并建图
for
int
i
=
0
int
course
=
0
int
pre
=
1
// 入度为 0 的点入队
for
int
i
=
0
if
0
true
if
return
false
// BFS 遍历
while
int
tmp
=
// 销毁边:减少邻居入度
for
int
// 入度为 0 且未访问过才入队
if
0
true
// 检查是否所有课程都能被访问
for
int
i
=
0
if
0
return
false
return
true

2.2 课程表 II(获取顺序)

题目核心:不仅要判断能否完成,还要返回具体的学习顺序。

解题逻辑: 基本思路与上一题一致,区别在于我们需要记录出队的顺序。我们可以用一个数组来存储结果,每弹出一个节点就填入数组。

注意边界情况:如果先修条件为空,直接返回 0 到 numCourses-1 的顺序即可。最后比较计数器与总课程数,不一致说明存在环,返回空数组。

class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        int[] result = new int[numCourses];
        int count = 0;
        
        int m = prerequisites.length;
        if (m == 0) {
            for (int i = 0; i < numCourses; i++) result[i] = i;
            return result;
        }
        
        List<List<Integer>> edges = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) {
            edges.add(new ArrayList<>());
        }
        
        Queue<Integer> queue = new LinkedList<>();
        boolean[] visited = new boolean[numCourses];
        int[] inDegree = new int[numCourses];
        
        // 建图与统计入度
        for (int i = 0; i < m; i++) {
            int course = prerequisites[i][0];
            int pre = prerequisites[i][1];
            edges.get(pre).add(course);
            inDegree[course]++;
        }
        
        // 初始入队
        for (int i = 0; i < numCourses; i++) {
            if (inDegree[i] == 0) {
                queue.add(i);
                visited[i] = true;
            }
        }
        
        // BFS 过程
        while (!queue.isEmpty()) {
            int tmp = queue.poll();
            result[count++] = tmp;
            
            for (int neighbor : edges.get(tmp)) {
                inDegree[neighbor]--;
                if (inDegree[neighbor] == 0 && !visited[neighbor]) {
                    queue.add(neighbor);
                    visited[neighbor] = true;
                }
            }
        }
        
        // 验证结果
        return (count != numCourses) ? new int[0] : result;
    }
}

2.3 火星词典(字符串转图)

题目核心:根据字典序推导字符顺序。

解题逻辑: 这道题稍微复杂一点,因为需要先通过字符串对比构建图。两个字符串第一个不同位置的字符决定了它们的大小关系,例如 "wrt" 和 "wrf" 意味着 t -> f。

需要特别处理一种非法情况:前缀冲突。比如 "abc" 排在 "ab" 前面,这是不合法的,因为短字符串不能比长字符串大。

class Solution {
    Map<Character, Set<Character>> edges = new HashMap<>();
    Map<Character, Integer> inDegree = new HashMap<>();
    boolean illegal = false;
    
    public String alienOrder(String[] words) {
        // 初始化所有字符入度为 0
        for (String s : words) {
            for (char ch : s.toCharArray()) {
                inDegree.put(ch, 0);
            }
        }
        
        // 建图
        for (int i = 0; i < words.length - 1; i++) {
            addEdge(words[i], words[i + 1]);
            if (illegal) return "";
        }
        
        // 拓扑排序初始化
        Queue<Character> queue = new LinkedList<>();
        for (char ch : inDegree.keySet()) {
            if (inDegree.get(ch) == 0) queue.add(ch);
        }
        
        // BFS 构建结果
        StringBuilder ret = new StringBuilder();
        while (!queue.isEmpty()) {
            char tmp = queue.poll();
            ret.append(tmp);
            
            if (edges.containsKey(tmp)) {
                for (char ch : edges.get(tmp)) {
                    inDegree.put(ch, inDegree.get(ch) - 1);
                    if (inDegree.get(ch) == 0) queue.add(ch);
                }
            }
        }
        
        // 检查是否有环
        return ret.length() != inDegree.size() ? "" : ret.toString();
    }
    
    private void addEdge(String a, String b) {
        int n = Math.min(a.length(), b.length());
        int i = 0;
        for (; i < n; i++) {
            char c1 = a.charAt(i);
            char c2 = b.charAt(i);
            if (c1 != c2) {
                edges.putIfAbsent(c1, new HashSet<>());
                if (!edges.get(c1).contains(c2)) {
                    edges.get(c1).add(c2);
                    inDegree.put(c2, inDegree.get(c2) + 1);
                }
                break;
            }
        }
        // 处理前缀非法情况:如 abc 在 ab 前面
        if (i == b.length() && a.length() > b.length()) {
            illegal = true;
        }
    }
}

目录

  1. 一、拓扑排序基础
  2. 1.1 有向无环图(DAG)
  3. 1.2 AOV 网:顶点活动图
  4. 1.3 什么是拓扑排序
  5. 1.4 BFS 实现思路
  6. 二、经典案例解析
  7. 2.1 课程表 I(检测可行性)
  8. 2.2 课程表 II(获取顺序)
  9. 2.3 火星词典(字符串转图)
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Python 性能优化的 8 个实用技巧
  • Android 及 Java 核心技术面试指南:高频考点与解析
  • 基于 FastAPI 的 Web 上位机系统设计与实现
  • Cursor 中配置和使用 MCP 服务详解
  • Linux 环境变量详解:概念、操作与代码获取
  • 机器人正运动学与逆运动学详解
  • Dify 集成 MySQL 实战:基于 MCP 协议的数据查询方案
  • Spring Boot 集成 JUnit 5 报错 NoSuchMethodError 排查与修复
  • AI 大模型微调详解:核心原理与主流方法解析
  • Android 集成 WebRTC 与 VAD 的 AI 辅助开发实战:从选型到性能优化
  • 前端国际化实战:i18next 与 react-intl 方案
  • 数据结构:栈和队列的相互实现
  • Eino 框架 Embedding 组件详解与使用
  • 网络安全基础:成为专业安全研究员需掌握的十大核心技能
  • 基于 Coze 构建专属 AI 应用:从智能体到 Web 部署实战
  • AI 大模型基础与前端开发面试准备指南
  • C++ 入门指南:基础语法与进阶实战
  • 医疗 AI 场景下算法编程深度解析
  • DataX 的两种部署方式以及 DataX-Web 可视化管理平台的搭建
  • 基于 Cursor 的 Web 版背单词应用开发实战

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online