拓扑排序算法原理及Java代码实现

2023-11-16

一、拓扑排序的概念

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。

拓扑(ta pu)排序:BFS + 贪心,专门用于解决任务调度、课程顺序问题;
核心元素:入度,即有向图中某个顶点作为终点的次数之和;

二、算法原理

  • 将问题转化为有向图描述,记录每个节点的后继节点、入度值;
  • 找出所有入度为0的节点(入度为0说明没有前驱结点),将他们入队(队列中始终存放入度为0的节点);
  • 循环操作:从队列中依次将入度0的节点出队放入结果集数组(存放最终排序结果),并将该节点的后继节点入度值-1。
  • 检查是否含有环路:检查是否有节点没有输出(即结果集是否包含所有的节点),如果有节点没有输出,说明存在环路。

三、需要使用的数据结构

  • 哈希表(邻接表)数组HashSet[]:存放每个节点的后继节点;adj[i] = new HashSet();//表示节点i的后继节点
  • 入度数组inDegree[]:每个节点对应的入度值(入度值就是有向图中某个节点作为终点的次数和,即前驱节点的数目)
  • 队列queue:存放入度值为0的节点
  • 结果集数组res[]:拓扑排序的结果,即线性序列

四、Java代码实现案例

1.课程学习顺序
案例描述:现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。

class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        //拓扑排序(BFS+贪心):专门用于解决任务调度问题和课程问题。
        //需要使用的数据结构:
        //哈希表(邻接表)数组HashSet[]:存放每个节点的后继节点;adj[i] = new HashSet();//表示课程i的后继节点课程
        //入度数组inDegree[]:每个节点对应的入度值(入度值就是有向图中某个节点作为终点的次数和,即前驱节点的数目)
        //队列queue:存放入度值为0的节点
        //结果集数组res[]:拓扑排序的结果,即课程学习顺序
        if(numCourses <= 0){
            return new int[0];
        }
        //1.将问题转化为有向图描述,记录每个节点的后继节点、入度值
        //1.1记录每个节点的后继节点(每个课程的后继课程)
        HashSet<Integer>[] adj = new HashSet[numCourses];//1.1.1定义哈希数组(邻接表):存放每个节点的后继节点;adj[i] = new HashSet();//表示课程i的后继节点课程
        for(int i = 0 ; i < numCourses ; i++){
            adj[i] = new HashSet<>();//1.1.2初始化哈希数组:初始化课程i的后继课程
        }
        //1.2记录每个节点的入度值(每个课程的前驱课程数目)
        int[] inDegree = new int[numCourses];//1.2.1定义并初始化入度数组inDegree[]:每个节点对应的入度值
        for(int[] p : prerequisites){
            //p[1] → p[0]
            adj[p[1]].add(p[0]);//1.1.3记录每个节点的后继节点
            inDegree[p[0]]++; //1.2.2记录每个节点的入度值
        }

        //2.找到入度为0的节点,将他们入队
        Queue<Integer> queue = new LinkedList<>();//定义队列queue:始终存放入度值为0的节点
        for(int i = 0 ; i < numCourses ; i++){
            if(inDegree[i] == 0) queue.offer(i);
        }

        //3.拓扑排序:每一次都找出入度值为0的节点,将其取出来放入结果集中,并将其后继节点入度值-1
        int[] res = new int[numCourses];//定义结果集数组res[]:拓扑排序的结果,即课程学习顺序
        int count = 0;//当前课程的排序顺序(即排序后当前课程的位置索引)
        while(!queue.isEmpty()){
            //3.1将队列中节点(即入度值为0的节点)依次出队,并放入结果集数组res
            Integer head = queue.poll();
            res[count] = head;//将入度0课程head放入结果数组索引count的位置上
            count++;//更新位置索引(排序顺序)
            //3.2后继节点的入度值-1
            Set<Integer> successors = adj[head];//得到入度0节点的后继节点
            for(int nextCourse : successors){
                inDegree[nextCourse]--;//入度值-1
                //立马检查入度值是否为0,如果是0就放入队列
                if(inDegree[nextCourse] == 0){
                    queue.offer(nextCourse);
                }
            }

        }

        //4.检测有向图是否有环:检查是否有节点没有输出(即结果集是否包含所有的节点),如果有节点没有输出,说明存在环路,则不能完成所有课程的学习。
        if(count == numCourses){
            return res;//如果结果集包含所有节点,那么排序成功结束,没有环路,可以完成所有课程的学习,返回结果集
        }
        return new int[0];//否则说明存在环路,无法完成所有课程的学习


    }
}

2.课程表
案例描述:
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        //1.定义入度值和依赖关系
        Map<Integer , Integer> inDegree = new HashMap<>();//1.1用哈希表存放对应课号的入度值(key:课号,value:入度值)
        for(int i = 0 ; i < numCourses ; i++){
            inDegree.put(i , 0);//1.2初始化入度值
        }
        Map<Integer , List<Integer>> adj = new HashMap();//1.3邻接表:用哈希表存放依赖关系(key:课号,value:依赖这门课的后续课程)
        
        //2.更新入度值和化依赖关系
        for(int[] relate : prerequisites){
            //举例说明:(3,0), 想学3号课程要先完成0号课程, 更新3号课程的入度和0号课程的依赖(邻接表)
            int cur = relate[1];
            int next = relate[0];
            //2.1更新入度值
            inDegree.put(next , inDegree.get(next) + 1);
            //2.2更新当前课程的依赖关系
            if(!adj.containsKey(cur)){//2.2.1如果邻接表中不存在当前课程cur,就要新开辟一个哈希空间
                adj.put(cur , new ArrayList<>());
            }
            adj.get(cur).add(next);//2.2.2添加当前课程cur的依赖关系
        }

        //3.BFS:首先将入度为 0 的课程放入队列(队列中的课程就是没有先修,可以直接学的课程);然后依次取出入度值为0的课程(学习入度值为0的课程),其后续依赖课程入度值需要-1;最后更新队列,再次循环。
        //3.1找到入度值为0 的课程,放入队列q中
        Queue<Integer> q = new LinkedList<>();//定义队列q用于存放入度值为0的课程
        //遍历入度值,找到入度值为0 的课程,放入队列中
        for(int key : inDegree.keySet()){
            if(inDegree.get(key) == 0){
                q.offer(key);
            }
        }
        //3.2从队列q取出一个节点,相当于学习这门课程,其后续依赖课程的入度值需要-1
        while(!q.isEmpty()){
            int cur = q.poll();//3.2.1取出一个节点,相当于学习这门课程
            if(!adj.containsKey(cur)){
                continue;
            }
            List<Integer> successorList = adj.get(cur);//3.2.2获取当前课程cur的邻接表
            for(int k : successorList){
                inDegree.put(k , inDegree.get(k) - 1);//3.2.3更新入度值:当前课程cur已学习,则后续课程入度值-1
                
                //3.3更新队列
                if(inDegree.get(k) == 0){
                    q.offer(k);//如果后续课程入度值为0,则加入队列q
                }
            }
        }

        //4.结束条件:如果存在入度值不为0的课程,则false;否则说明全部课程入度值都为0,都可以直接学习了,ture
        for (int key : inDegree.keySet()) {
            if (inDegree.get(key) != 0) {
                return false;
            }
        }
        return true;



    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

拓扑排序算法原理及Java代码实现 的相关文章

  • 用于解析和构建逻辑表达式的 Java 库

    我正在寻找一个 Java 开源库来解析和构建类似 SQL 的表达式 例如评估表达式的有效性 例如 a x or y and b z 另外我想要一个用于构建或扩展表达式的 API 就像是 Expression exp new Expressi
  • 策略模式还是命令模式?

    假设我有一个金融交易列表 我需要针对这些交易执行一系列验证规则 一个例子是我有一笔购买产品的交易 但是首先我需要验证交易中的帐户是否有足够的可用资金 产品没有售完等 由于这些规则 交易将是标记为拒绝 并应指定错误代码 当然 我正在考虑用一个
  • 将 MouseListener 添加到面板

    我正在尝试将鼠标操作添加到我的面板中 这就是程序应该做的事情 编写一个程序 允许用户通过按三下鼠标来指定一个三角形 第一次按下鼠标后 画一个小点 第二次按下鼠标后 绘制一条连接前两个点的线 第三次按下鼠标后 绘制整个三角形 第四次按下鼠标会
  • 使类只能从特定类实例化

    假设我有 3 节课class1 class2 and class3 我怎样才能拥有它class1只能通过实例化class2 class1 object new class1 但不是 class3 或任何其他类 我认为它应该与修饰符一起使用
  • 重写 getPreferredSize() 会破坏 LSP

    我总是在这个压倒一切的网站上看到建议getPreferredSize 而不是使用setPreferredSize 例如 如前面的线程所示 对于固定大小的组件 使用重写 getPreferredSize 而不是使用 setPreferredS
  • 无法访问“不安全”java方法的java表达式语言

    我正在开发一个项目 让用户向服务器提交小 脚本 然后我将执行这些脚本 有很多脚本语言可以嵌入到Java程序中 例如mvel ognl uel clojure rhino javascript等 但是 据我所知 它们都允许脚本编写者调用Jav
  • 无法在 Java 中输出正确的哈希值。怎么了?

    在我的 Android 应用程序中 我有一个 SHA256 哈希值 我必须使用 RIPEMD160 消息摘要算法进一步对其进行哈希值 我可以输出任何字符串的正确 sha256 和ripemd160 哈希值 但是当我尝试使用ripemd160
  • 确定序列化对象的类型

    我需要通过套接字发送消息 从用户到引擎的请求 以及从引擎到用户的响应 所以流程本质上是 serialized request Server lt network gt Client serialized response request r
  • 具有 JPA 持久性的 Spring 状态机 - 存储库使用

    我试图弄清楚如何轻松使用 Spring 状态机 包括使用 JPA 进行持久化 这是我正在处理的问题 不兼容的数据类型 工厂和持久性 在程序的某个时刻 我想使用连接到用户的状态机 有用于此目的的存储库 项目spring statemachin
  • 如何在 JPA 和 Hibernate 中将数据库生成的列值定义为只读字段?

    使用 MariaDB 10 2 可以定义日期时间的默认值 例如创建和最后修改 我应该如何将此列作为只读字段访问 因为这个值应该只在数据库的控制之下 并且不应该从代码中修改 但我想在代码中读取这个属性 这很简单 只需设置insertable
  • 在 java 中运行外部应用程序但不要等待它完成

    我正在用java编写一个应用程序 允许我运行其他应用程序 为此 我使用了 Process 类对象 但当我这样做时 应用程序会等待进程结束 然后再退出 有没有办法在 Java 中运行外部应用程序 但不等待它完成 public static v
  • HTTP 状态 405 - 此 URL java servlet 不支持 HTTP 方法 POST [重复]

    这个问题在这里已经有答案了 我无法使页面正常工作 我有要发布的表单方法和我的 servlet 实现doPost 然而 它不断地向我表明我并不支持POST方法 我只是想做一个简单的网站并将值插入到我的 MySQL 数据库中 type Stat
  • 删除 JFX 中选项卡后面的灰色背景

    So is there any way to remove the gray area behind the tab s 我尝试过用 CSS 来做到这一点 但没有找到方法 要设置 tabpane 标题的背景颜色 请在 CSS 文件中写入 t
  • java中使用多线程调用同一类的不同方法

    我有一个类 如下所示 具有三种方法 public class MyRunnable implements Runnable Override public void run what code need to write here to c
  • 在实现使用原始类型的接口时如何避免警告?

    我正在实施流程工厂 http help eclipse org ganymede index jsp topic org eclipse platform doc isv reference api org eclipse debug co
  • 受信任的 1.5 小程序可以执行系统命令吗?

    如果是的话 这个能力有什么限制吗 具体来说 我需要以 Mac OSX 为目标 我以前用过这个在 Windows 系统上启动东西 但从未在 Mac 上尝试过 public void launchScript String args Strin
  • Java/MongoDB 按日期查询

    我将一个值作为 java util Date 存储在我的集合中 但是当我查询以获取两个特定日期之间的值时 我最终得到的值超出了范围 这是我的代码 插入 BasicDBObject object new BasicDBObject objec
  • 为什么java.lang.Cloneable不重写java.lang.Object中的clone()方法?

    Java 规范java lang Cloneable接口将自身定义为表示扩展它的任何对象也实现了clone 休眠的方法java lang Object 具体来说 它说 一个类实现了Cloneable接口来指示java lang Object
  • Java中单例的其他方式[重复]

    这个问题在这里已经有答案了 只是我在考虑编写单例类的其他方法 那么这个类是否被认为是单例类呢 public class MyClass static Myclass myclass static myclass new MyClass pr
  • java中void的作用是什么?

    返回类型 方法返回值的数据类型 如果方法不返回值 则返回 void http download oracle com javase tutorial java javaOO methods html http download oracle

随机推荐

  • 【目标检测】38、PAA

    文章目录 一 背景 二 方法 2 1 Probabilistic Anchor Assignment Algorithm 2 2 IoU prediction as Localization Quality 2 3 Score Voting
  • ctfshow 网络迷踪-哐啷哐啷+鲶鱼之谜

    12 哐啷哐啷 通过谷歌识图找到这张图片出自的文章 得到这是这是新疆和田 ctfshow 和田 13 鲶鱼之谜 这题在群里有wp我就不写了各位可以参考一下群文件 最终flag ctfshow ca1524 1837
  • 运行jupyter notebook 时报“kernel error“ ,以及如何切换python解释器

    运行jupyter notebook 时报 kernel error 以及如何切换python解释器 一般出现 kernel error 时 多半是因为系统中存在较多的python解释器 1 查看内核 假定读者已通过pip 安装了 jupy
  • uni-app微信小程序-利用canvas给图片添加水印

    实现思路 一 选择图片 二 将图片绘制到 canvas 中并绘制水印 三 将 canvas 画布转换为图片地址 四 最终效果 五 完整代码 实现思路 选择图片 将图片绘制到 canvas 中并绘制水印 将添加水印的图片绘制到 canvas
  • 2023最新的Java八股文合集来了,彻底解决一线大厂面试难题

    纵观今年的技术招聘市场 Java 依旧是当仁不让的霸主 即便遭受 Go 等新兴语言不断冲击 依旧岿然不动 究其原因 Java 有着极其成熟的生态 这个不用我多说 Java 在 运维 可观测性 可监 控性方面都有着非常优秀的表现 Java 也
  • python学习笔记---错误、调试和测试【廖雪峰】

    错误 调试和测试 三类错误 有的错误是程序编写有问题造成的 比如本来应该输出整数结果输出了字符串 这种错误我们通常称之为bug bug是必须修复的 有的错误是用户输入造成的 比如让用户输入email地址 结果得到一个空字符串 这种错误可以通
  • android ApplicationInfo类

    1 获取apk文件的图标 java view plain copy print public static Drawable getApkFileIcon String apkPath Context context PackageMana
  • ACM--田忌赛马--贪心--HDOJ 1052--Tian Ji -- The Horse Racing

    HDOJ题目地址 传送门 Tian Ji The Horse Racing Time Limit 2000 1000 MS Java Others Memory Limit 65536 32768 K Java Others Total S
  • 实战wxPython:059 - GDI基本元素之字体Font

    字体是决定文本外观的对象 字体用于将文本绘制到设备上下文 并设置窗口文本的外观 一 wx Font简介 创建自定义字体最简单的方法是使用wx FontInfo对象指定字体属性 然后使用wx Font构造函数来创建 wx Font的构造函数形
  • 软件测试实验:静态测试

    目录 前言 一 实验目的 二 实验内容 三 实验步骤 四 实验过程 1 学生宿舍管理系统代码 2 汇总表 3 C语言编码规范 总结 前言 软件测试是软件开发过程中不可或缺的一个环节 它可以保证软件的质量和功能 提高用户的满意度和信任度 软件
  • Win11系统提示缺少msvcp140_clr0400.dll文件的解决办法

    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题 如果是新手第一时间会认为是软件或游戏出错了 其实并不是这样 其主要原因就是你电脑系统的该dll文件丢失了或者损坏了 这时你只需下载这个msvcp140 clr0400 dll文件进行
  • 第二篇论文写作启发点V1

    第二篇论文写作启发点V1 2 LLFLow模型的缺陷 这是先验 如果先验出现错误 那么后面这个模型都会错误 而我们使用了学习的方式去解决 3 参考文献和实验时的对照模型最好使用最新的 就是没有被引用过的 这样可以降低论文的重复率 4 E表示
  • windows server 2012 显示控制面板

    rundll32 exe shell32 dll Control RunDLL desk cpl 0
  • 出现Permission denied的解决办法(750权限谨慎使用)

    如果不明白其中的含义 请勿使用此命令 提示 Permission denied 解决的办法 注意 777权限谨慎使用 为高权限 sudo chmod R xxx 某一目录 自己定义xxx 下面有说明 其中 R 是指级联应用到目录里的所有子目
  • &运算(位运算)

    给大家举个列子 8的二进制是1000 7的二进制是0111 1000 0111 运算中1 1 1 1 0 0 0 0 0 并且按位置对应运算的 也就是说第一位和第一位运算 其他类推 所以可以得出0000 故输出的是0
  • 《Git进阶:掌握版本控制的高级技巧》

    博主猫头虎 带您 Go to New World 博客首页 猫头虎的博客 面试题大全专栏 文章图文并茂 生动形象 简单易学 欢迎大家来踩踩 IDEA开发秘籍专栏 学会IDEA常用操作 工作效率翻倍 100天精通Golang 基础入门篇 学会
  • C语言:助学贷款额度计算

    大学可以申请助学贷款 申请额度不超过学费和生活费总额的60 分两行依次输入每学分应缴纳的学费 整数 单位为元 和你每个月的生活费 浮点数 单位为元 请计算你每个学期能够贷款多少元 结果保留小数点后2位数字 每个学期按5个月计算 输出格式请参
  • 国际版腾讯云/阿里云:全站加快有哪些功用?有哪些优势?适用于什么场景?

    腾讯云全站加快有哪些功用 有哪些优势 适用于什么场景 产品功用 全站加快 ECDN 经过在全球各区域部署加快节点 有用下降跨国拜访推迟 保证全球加快作用 最优链路 各加快节点两两相连 实时勘探 结合腾讯自研的最优链路算法 获取用于传输的最优
  • matplotlib绘制折线图与散点图

    coding utf 8 import numpy as np import matplotlib pyplot as plt 折线图 figure no表示显示图的顺序 def simple line plot x y figure no
  • 拓扑排序算法原理及Java代码实现

    一 拓扑排序的概念 对一个有向无环图 Directed Acyclic Graph简称DAG G进行拓扑排序 是将G中所有顶点排成一个线性序列 使得图中任意一对顶点u和v 若边