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

使用 BFS 实现拓扑排序

基于广度优先搜索(BFS)的拓扑排序算法。首先讲解了有向无环图(DAG)、AOV 网及拓扑排序的基本概念与实现步骤。随后通过三个经典例题进行实战演练:LeetCode 207 课程表判断能否完成课程,210 课程表 II 返回具体学习顺序,以及 LCR 114 火星词典根据字典序重建字母顺序。文章提供了完整的 Java 代码实现,包含建图、入度统计及队列处理逻辑,适用于解决依赖关系排序问题。

PhpPioneer发布于 2026/3/29更新于 2026/10/25115 浏览
使用 BFS 实现拓扑排序

拓扑排序

有向无环图(DAG)

有向无环图:一个无回路的有向图,如果一个有向图无法从某个顶点出发经过若干条边回到该点,则这个图是一个有向无环图(DAG 图)。

文章配图

AOV 网:顶点活动图

在有向无环图的基础上,用顶点来表示一个活动,用边来表示活动执行的先后顺序。

文章配图

拓扑排序

拓扑排序:找到做事的先后顺序。每次取出入度为 0 的点,并且删除该点相连的边,排序进行。

实现拓扑排序

  1. 找出图中入度为 0 的点
  2. 删除与该点连接的边
  3. 重复上述步骤直到没有点或者没有入度为 0 的点

借助队列,来一次 BFS:

  1. 初始化:把所有入度为 0 的点加入到队列
  2. 当队列不为空:
    1. 拿出队头元素,加入到最终结果
    2. 删除与元素相连的边
    3. 判断:与删除的边相连的点,入度是否变为 0

207. 课程表

题目链接:207. 课程表

题目描述:

文章配图

题目解析:

  • 给我们一个 int 数,代表 id 为 0 到 numCourses 的课程
  • 给我们一个 prerequisites 数组,其中从后往前代表学习顺序
  • 让我们看能否排课,将课程学完,必须保证学习顺序

解题思路:

  • 我们使用二维数组 List,来表示图,每一个下标对应的一维数组,表示该点指向的点
  • 我们遍历一次 prerequisites 数组,将最后的 n-1 列的值当成点,其余入该点对应的一维数组。除了 n-1 列,其余对应的入度数组对应下标值加一
  • 在进行一次 BFS 即可
  • 注意事项:每个点只能入一次队,prerequisites 可以是 [] 数组,在前面要排除

解题代码:

class Solution {
    public boolean canFinish( numCourses, [][] prerequisites) {
           prerequisites.length;
         (m == )  ;
           prerequisites[].length;
        
        List<List<Integer>> edges =  <>();
         (   ; i < numCourses; i++) {
            edges.add( <>());
        }
        Queue<Integer> queue =  <>();
        
        [] in =  [numCourses];
        
        [] flag =  [numCourses];
        
        
         (   ; i < m; i++) {
             (   ; j < n - ; j++) {
                
                edges.get(prerequisites[i][n - ]).add(prerequisites[i][j]);
                
                in[prerequisites[i][j]]++;
            }
        }
        
        
         (   ; i < numCourses; i++) {
             (in[i] == ) {
                queue.add(i);
                flag[i] = ;
            }
        }
        
         (queue.isEmpty())  ;
        
        
         (!queue.isEmpty()) {
               queue.poll();
            
             (   ; i < edges.get(tmp).size(); i++) {
                in[edges.get(tmp).get(i)]--;
            }
            
             (   ; i < numCourses; i++) {
                 (in[i] ==  && !flag[i]) {
                    queue.add(i);
                    flag[i] = ;
                }
            }
        }
        
        
         (   ; i < numCourses; i++) {
             (in[i] != ) {
                 ;
            }
        }
         ;
    }
}
int
int
int
m
=
if
0
return
true
int
n
=
0
// 建图
new
ArrayList
for
int
i
=
0
new
ArrayList
new
LinkedList
// 记录下标对应每个点的入度
int
new
int
// 标记入过队的元素
boolean
new
boolean
// 记录入度
for
int
i
=
0
for
int
j
=
0
1
// 建图
1
// 统计入度
// 入度为 0 的点入队
for
int
i
=
0
if
0
true
if
return
false
// BFS
while
int
tmp
=
// 销毁边
for
int
i
=
0
// 入度为 0 的点入队
for
int
i
=
0
if
0
true
// 还有入度不为 0 的点
for
int
i
=
0
if
0
return
false
return
true

210. 课程表 II

题目链接:210. 课程表 II

题目描述:

文章配图

题目解析:

  • 给我们一个 int 数,代表 id 为 0 到 numCourses 的课程
  • 给我们一个 prerequisites 数组,其中从后往前代表学习顺序
  • 让我们看能否排课,将课程学完,必须保证学习顺序,返回学习的顺序,不行就返回空数组

解题思路:

  • 我们使用二维数组 List,来表示图,每一个下标对应的一维数组,表示该点指向的点
  • 我们遍历一次 prerequisites 数组,将最后的 n-1 列的值当成点,其余入该点对应的一维数组。除了 n-1 列,其余对应的入度数组对应下标值加一
  • 在进行一次 BFS 即可,每次出的点都进入结果数组
  • 注意事项:
    • 我们先将结果数组初始化为 0 到 numCourses-1,prerequisites 可以是 [] 数组时直接就返回
    • 当最后执行完,我们使用一个计数器 lep 来标记进入结果数组的元素,最后只需要比较 lep 与 numCourses 是否一样,一样返回结果数组,不一样返回空数组

解题代码:

class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        // 结果数组
        int[] ret = new int[numCourses];
        for (int i = 0; i < numCourses; i++) ret[i] = i;
        
        int m = prerequisites.length;
        if (m == 0) return ret;
        
        int n = prerequisites[0].length;
        Queue<Integer> queue = new LinkedList<>();
        // 标记数组
        boolean[] flag = new boolean[numCourses];
        // 入度数组
        int[] in = new int[numCourses];
        // 建图
        List<List<Integer>> edges = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) {
            edges.add(new ArrayList<>());
        }
        
        // 记录入度,初始图
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n - 1; j++) {
                edges.get(prerequisites[i][n - 1]).add(prerequisites[i][j]);
                in[prerequisites[i][j]]++;
            }
        }
        
        // 入度为 0 的点入队
        for (int i = 0; i < numCourses; i++) {
            if (in[i] == 0) {
                flag[i] = true;
                queue.add(i);
            }
        }
        
        // BFS
        int lep = 0;
        while (!queue.isEmpty()) {
            int tmp = queue.poll();
            ret[lep++] = tmp;
            // 删去该点出的边
            for (int i = 0; i < edges.get(tmp).size(); i++) {
                in[edges.get(tmp).get(i)]--;
            }
            // 入队
            for (int i = 0; i < numCourses; i++) {
                if (in[i] == 0 && !flag[i]) {
                    queue.add(i);
                    flag[i] = true;
                }
            }
        }
        
        if (lep != numCourses) return new int[]{};
        return ret;
    }
}

LCR 114. 火星词典

题目链接:LCR 114. 火星词典

题目描述:

文章配图

题目解析:

  • 给我们一个字符串数组 words
  • 两个字符串对比来得出字母顺序,两个字符串第一个不相同的字母的顺序等于两个字符串在数组中的顺序,如示例一第一个字符串'wrt'和第二个字符串'wrf'得出的字母顺序就是 t->f
  • 将所有的字符两两对比,得出字母顺序,不能得出线性顺序就返回空字符串''

解题思路:

  • 两层 for 循环,先搜索两个规则导致的排序结果建立图,例如示例一就建立这个图
  • 根据图拓扑排序,得出结论
  • 细节问题:abc 和 ab,并且 abc 在数组中位于 ab 前面,这种是不合法的

解题代码:

文章配图

class Solution {
    // 图
    Map<Character, Set<Character>> edges = new HashMap<>();
    // 入度
    Map<Character, Integer> in = new HashMap<>();
    // 标记有没有 abc ab 这种不合法
    boolean flag;

    public String alienOrder(String[] words) {
        // 初始化入度
        for (String s : words) {
            for (int i = 0; i < s.length(); i++) {
                char ch = s.charAt(i);
                in.put(ch, 0);
            }
        }
        
        // 建图
        for (int i = 0; i < words.length; i++) {
            for (int j = i + 1; j < words.length; j++) {
                add(words[i], words[j]);
                if (flag == true) return "";
            }
        }
        
        // 拓扑排序初始化
        Queue<Character> queue = new LinkedList<>();
        for (char ch : in.keySet()) {
            if (in.get(ch) == 0) queue.add(ch);
        }
        
        // BFS
        StringBuffer ret = new StringBuffer();
        while (!queue.isEmpty()) {
            char tmp = queue.poll();
            ret.append(tmp);
            if (!edges.containsKey(tmp)) continue;
            for (char ch : edges.get(tmp)) {
                in.put(ch, in.get(ch) - 1);
                if (in.get(ch) == 0) {
                    queue.add(ch);
                }
            }
        }
        
        for (char ch : in.keySet()) {
            if (in.get(ch) != 0) return "";
        }
        return ret.toString();
    }

    public void add(String a, String b) {
        int i = 0;
        int n = Math.min(a.length(), b.length());
        for (; i < n; i++) {
            char ch1 = a.charAt(i);
            char ch2 = b.charAt(i);
            if (ch1 != ch2) {
                // ch1 -> ch2
                if (!edges.containsKey(ch1)) {
                    edges.put(ch1, new HashSet<>());
                }
                if (!edges.get(ch1).contains(ch2)) {
                    edges.get(ch1).add(ch2);
                    in.put(ch2, in.get(ch2) + 1);
                }
                break;
            }
        }
        if (i == b.length() && a.length() > i) flag = true;
    }
}

目录

  1. 拓扑排序
  2. 有向无环图(DAG)
  3. AOV 网:顶点活动图
  4. 拓扑排序
  5. 实现拓扑排序
  6. 207. 课程表
  7. 210. 课程表 II
  8. LCR 114. 火星词典

更多推荐文章

查看全部
  • Java ArrayList 底层方法自我实现
  • llama.cpp 多 GPU 分布式计算优化实践
  • STM32 中__weak 弱定义函数核心总结
  • CTF 逆向:RC4、TEA 与 Base64 加密算法特征识别与魔改分析
  • 生信零基础到独立项目:3 个月模块化学习计划
  • MiniMax 海螺 AI:图片与文本生成高质量视频指南
  • Python 字符串格式化:% 运算符详解
  • 《Agent Runtime 工程化》第一章 Agent Runtime 全景:导读
  • LazyLLM 多 Agent 应用实战:源码部署与可视化调试指南
  • 数据结构:双向链表详解与实现
  • GLM-4.5-Air-Base 开源:1060 亿参数智能推理模型免费商用
  • 进阶数据结构:AVL 树
  • llama.cpp 多 GPU 分布式计算优化指南
  • 基于 Java SpringBoot 的企业设备信息一体化管理系统
  • Spring Boot 整合 WebService 客户端:基于 WSDL 自动生成代码
  • DeepSeek-R1-Distill-Llama-8B 本地部署与 Docker Compose 服务化
  • 前端 Base64 格式文件上传详解:原理、实现与最佳实践
  • 人脸识别安全审计:基于 RetinaFace 与 CurricularFace 构建测试环境
  • CCF-GESP 一级 C++ 真题解析:手机电量显示
  • AI 编程工具收费模式变革:Token 计费时代的开发者生存指南

相关免费在线工具

  • 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