【算法基础:搜索与图论】3.3 拓扑排序

文章目录

  • 拓扑排序介绍
    • 如何构造拓扑排序(⭐重要!)
  • 例题:848. 有向图的拓扑序列
    • BFS 写法构造拓扑排序
  • 相关题目练习
    • 207. 课程表(判断是否存在拓扑序列)
      • bfs 写法
      • dfs 写法
    • 210. 课程表 II(找到一个拓扑序列)
    • 1136. 并行课程(找拓扑序列过程中记录最少学期数)
    • 2050. 并行课程 III(边带值的拓扑序列,好题!🐂)
    • 444. 序列重建(将问题转换成拓扑排序)
    • 269. 火星词典(需要考虑情况比较多的题目,需要细心)
  • 相关资料

拓扑排序介绍

https://oi-wiki.org/graph/topo/
本文主要学习拓扑排序相关知识。

拓扑排序的英文名是 Topological sorting。

拓扑排序要解决的问题是给一个 有向无环图所有节点排序

我们可以拿大学每学期排课的例子来描述这个过程,比如学习大学课程中有:程序设计,算法语言,高等数学,离散数学,编译技术,普通物理,数据结构,数据库系统等。按照例子中的排课,当我们想要学习 数据结构 的时候,就必须先学会 离散数学 和 编译技术。当然还有一个更加前的课程 算法语言。这些课程就相当于几个顶点 u, 顶点之间的有向边 (u,v) 就相当于学习课程的顺序。教务处安排这些课程,使得在逻辑关系符合的情况下排出课表,就是拓扑排序的过程。
在这里插入图片描述
但是如果某一天排课的老师打瞌睡了,说想要学习 数据结构,还得先学 操作系统,而 操作系统 的前置课程又是 数据结构,那么到底应该先学哪一个(不考虑同时学习的情况)?在这里,数据结构 和 操作系统 间就出现了一个环,显然你现在没办法弄清楚你需要学什么了,于是你也没办法进行拓扑排序了。因为如果有向图中存在环路,那么我们就没办法进行 拓扑排序。

因此我们可以说 在一个 DAG(有向无环图) 中,我们将图中的顶点以线性方式进行排序,使得对于任何的顶点 u 到 v 的有向边 (u,v), 都可以有 u 在 v 的前面。

还有给定一个 DAG,如果从 i 到 j 有边,则认为 j 依赖于 i。如果 i 到 j 有路径(i 可达 j),则称 j 间接依赖于 i。

拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点。


由于有环的图没有拓扑序列,因此拓扑排序还可以判断图中是否有环。

如何构造拓扑排序(⭐重要!)

  1. 从图中选择一个入度为零的点。
  2. 输出该顶点,从图中删除此顶点及其所有的出边。

重复上面两步,直到所有顶点都输出,拓扑排序完成,或者图中不存在入度为零的点,此时说明图是有环图,拓扑排序无法完成,陷入死锁。

例题:848. 有向图的拓扑序列

在这里插入图片描述

BFS 写法构造拓扑排序

思路按照:

  1. 从图中选择一个入度为零的点。
  2. 输出该顶点,从图中删除此顶点及其所有的出边。

重复上面两步,直到所有顶点都输出,拓扑排序完成,或者图中不存在入度为零的点,此时说明图是有环图,拓扑排序无法完成,陷入死锁。

import java.util.*;public class Main {static List<Integer>[] g;       // 用来建图static int[] in;                // 存储各个节点的入度static int n, m;public static void main(String[] args){Scanner scanner = new Scanner(System.in);n = scanner.nextInt();m = scanner.nextInt();g = new ArrayList[n + 1];in = new int[n + 1];Arrays.setAll(g, e -> new ArrayList<Integer>());for (int i = 0; i < m; ++i) {int a = scanner.nextInt(), b = scanner.nextInt();g[a].add(b);in[b]++;    // b 的入度 + 1}bfs();}static void bfs() {Queue<Integer> q = new LinkedList<>();boolean[] st = new boolean[n + 1];// 将所有初始入度 = 0 的节点放入队列for (int i = 1; i <= n; ++i) {if (in[i] == 0) {q.offer(i);st[i] = true;}}List<Integer> res = new ArrayList<>();while (!q.isEmpty()) {int x = q.poll();res.add(x);for (int y: g[x]) {                 // 将 x 的子节点 y 的入度 - 1in[y]--;if (!st[y] && in[y] == 0) {st[y] = true;q.offer(y);}}}if (res.size() == n) {                  // 如果都放进序列了,说明存在拓扑序列for (int v: res) System.out.print(v + " ");} else System.out.println("-1");;}
}

这道题是有向无环图,因此是可以不使用 st 数组存储节点是否已经被访问过。修改后的代码如下:

import java.util.*;public class Main {static List<Integer>[] g;       // 用来建图static int[] in;                // 存储各个节点的入度static int n, m;public static void main(String[] args){Scanner scanner = new Scanner(System.in);n = scanner.nextInt();m = scanner.nextInt();g = new ArrayList[n + 1];in = new int[n + 1];Arrays.setAll(g, e -> new ArrayList<Integer>());for (int i = 0; i < m; ++i) {int a = scanner.nextInt(), b = scanner.nextInt();g[a].add(b);in[b]++;    // b 的入度 + 1}bfs();}static void bfs() {Queue<Integer> q = new LinkedList<>();// 将所有初始入度 = 0 的节点放入队列for (int i = 1; i <= n; ++i) {if (in[i] == 0) q.offer(i);}List<Integer> res = new ArrayList<>();while (!q.isEmpty()) {int x = q.poll();res.add(x);for (int y: g[x]) {                 // 将 x 的子节点 y 的入度 - 1if (--in[y] == 0) q.offer(y);}}if (res.size() == n) {                  // 如果都放进序列了,说明存在拓扑序列for (int v: res) System.out.print(v + " ");} else System.out.println("-1");;}
}

相关题目练习

207. 课程表(判断是否存在拓扑序列)

https://leetcode.cn/problems/course-schedule/

在这里插入图片描述

bfs 写法

class Solution {List<Integer>[] g;int[] in;public boolean canFinish(int numCourses, int[][] prerequisites) {g = new ArrayList[numCourses];Arrays.setAll(g, e -> new ArrayList<Integer>());in = new int[numCourses];for (int[] prerequisity: prerequisites) {g[prerequisity[0]].add(prerequisity[1]);in[prerequisity[1]]++;}int sum = 0;Queue<Integer> q = new LinkedList<Integer>();for (int i = 0; i < numCourses; ++i) {if (in[i] == 0) {q.offer(i);sum++;}}while (!q.isEmpty()) {int x = q.poll();for (int y: g[x]) {if (--in[y] == 0) {sum++;q.offer(y);}} }return sum == numCourses;}
}

dfs 写法

相比于 bfs 写法, dfs 写法多开了一个 st 数组用来存储各个节点是否已经被访问过了。

class Solution {List<Integer>[] g;boolean[] st;int[] in;int sum = 0;public boolean canFinish(int numCourses, int[][] prerequisites) {g = new ArrayList[numCourses];Arrays.setAll(g, e -> new ArrayList<Integer>());in = new int[numCourses];st = new boolean[numCourses];for (int[] prerequisity: prerequisites) {g[prerequisity[0]].add(prerequisity[1]);in[prerequisity[1]]++;}for (int i = 0; i < numCourses; ++i) {if (in[i] == 0) {dfs(i);}}return sum == numCourses;}public void dfs(int x) {if (st[x]) return;++sum;st[x] = true;for (int y: g[x]) {if (--in[y] == 0) {dfs(y);}}}
}

210. 课程表 II(找到一个拓扑序列)

https://leetcode.cn/problems/course-schedule-ii/description/
在这里插入图片描述

对上一题的代码进行简单修改即可,把找到的拓扑序列存下来。

class Solution {List<Integer>[] g;List<Integer> ans = new ArrayList();int[] in;public int[] findOrder(int numCourses, int[][] prerequisites) {g = new ArrayList[numCourses];Arrays.setAll(g, e -> new ArrayList<Integer>());in = new int[numCourses];for (int[] prerequisity: prerequisites) {g[prerequisity[1]].add(prerequisity[0]);in[prerequisity[0]]++;}Queue<Integer> q = new LinkedList<Integer>();for (int i = 0; i < numCourses; ++i) {if (in[i] == 0) {q.offer(i);ans.add(i);}}while (!q.isEmpty()) {int x = q.poll();for (int y: g[x]) {if (--in[y] == 0) {ans.add(y);q.offer(y);}} }if (ans.size() == numCourses) return ans.stream().mapToInt(Integer::intValue).toArray();else return new int[]{};}
}

上一题是用 sum 记录可以学的课程数目,这一题是把可学的课程都放进 ans 列表中。

另外还有一道题:630. 课程表 III,但是和 拓扑排序 没什么关系。

1136. 并行课程(找拓扑序列过程中记录最少学期数)

https://leetcode.cn/problems/parallel-courses/

在这里插入图片描述

在 bfs 的过程中记录走了几步即可。

class Solution {List<Integer>[] g;int[] in;public int minimumSemesters(int n, int[][] relations) {in = new int[n + 1];g = new ArrayList[n + 1];Arrays.setAll(g, e -> new ArrayList());for (int[] r: relations) {g[r[0]].add(r[1]);in[r[1]]++;}int ans = 0, sum = 0;           // ans记录答案,sum记录学了几个课程Queue<Integer> q = new LinkedList();for (int i = 1; i <= n; ++i) {if (in[i] == 0) {q.offer(i);sum++;}}while (!q.isEmpty()) {int sz = q.size();for (int i = 0; i < sz; ++i) {int x = q.poll();for (int y: g[x]) {if (--in[y] == 0) {++sum;q.offer(y);}}}++ans;}return sum == n? ans: -1;}
}

还有一道题目1494. 并行课程 II 可见:从集合论到位运算——常见位运算技巧及相关习题 & 状态压缩DP

2050. 并行课程 III(边带值的拓扑序列,好题!🐂)

https://leetcode.cn/problems/parallel-courses-iii/

在这里插入图片描述

是 https://leetcode.cn/problems/parallel-courses/ 的进阶版,学完每门课程的需要花费的时间是不一样的。

借助优先队列,每次取出队列中最先完成的课程,检查完成这个课程后是否有新的课程可以开始学习即可。

最终的答案就是完成最后一门课程时的时间。

class Solution {List<Integer>[] g;int[] in;public int minimumTime(int n, int[][] relations, int[] time) {in = new int[n + 1];g = new ArrayList[n + 1];Arrays.setAll(g, e -> new ArrayList());for (int[] r: relations) {g[r[0]].add(r[1]);in[r[1]]++;}int ans = 0;           // ans记录答案// 按照完成时间升序排序的优先队列PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[1] - b[1]);for (int i = 1; i <= n; ++i) {if (in[i] == 0) {pq.offer(new int[]{i, time[i - 1]});}}while (!pq.isEmpty()) {int[] cur = pq.poll();ans = cur[1];for (int y: g[cur[0]]) {if (--in[y] == 0) {pq.offer(new int[]{y, ans + time[y - 1]});}}}return ans;}
}

444. 序列重建(将问题转换成拓扑排序)

https://leetcode.cn/problems/sequence-reconstruction/
在这里插入图片描述

题目数据保证了所有 sequences[i] 都是 nums 的子序列,因此 nums 一定是一个超序列,如果序列有唯一一个超序列,那么这一唯一的超序列一定是 nums

在构造拓扑序列的过程中,如果每次只有一个节点的入度是 0 ,那么构造的拓扑序列就是唯一的,则答案返回 true,否则中间过程一旦检查到同时有两个节点的入度变成了 0,那么就返回 false。

class Solution {public boolean sequenceReconstruction(int[] nums, List<List<Integer>> sequences) {int n = nums.length;List<Integer>[] g = new ArrayList[n + 1];int[] in = new int[n + 1];Arrays.setAll(g, e -> new ArrayList<Integer>()); for (List<Integer> s: sequences) {for (int i = 0; i < s.size() - 1; i++) {g[s.get(i)].add(s.get(i + 1));in[s.get(i + 1)]++;}}Queue<Integer> q = new LinkedList<Integer>();boolean f = false;int sum = 0;for (int i = 1; i <= n; ++i) {if (in[i] == 0) {++sum;if (f) return false;f = true;q.offer(i);}}while (!q.isEmpty()) {if (q.size() > 1) return false;int x = q.poll();for (int y: g[x]) {if (--in[y] == 0) {q.offer(y);sum++;}}}return sum == n;}
}

269. 火星词典(需要考虑情况比较多的题目,需要细心)

https://leetcode.cn/problems/alien-dictionary/

在这里插入图片描述

提示:

1 <= words.length <= 100
1 <= words[i].length <= 100
words[i] 仅由小写英文字母组成

这题比较麻烦,有多个情况需要考虑。

class Solution {public String alienOrder(String[] words) {Set<Character>[] g = new HashSet[128];Arrays.setAll(g, e -> new HashSet<Character>());int n = words.length;int[] in = new int[128];Set<Character> letters = new HashSet();// 记录所有出现的字符for (String word: words) {for (char ch: word.toCharArray()) letters.add(ch);}// 构造字符之间的顺序关系for (int i = 0; i < n; ++i) {for (int j = i + 1; j < n; ++j) {String a = words[i], b = words[j];for (int k = 0; k < a.length(); ++k) {if (k >= b.length()) return "";         // 前面的字符都一样但长的排在前面了不合理char x = a.charAt(k), y = b.charAt(k);if (x != y) {     if (g[y].contains(x)) return "";    // 和之前的顺序冲突了if (!g[x].contains(y)) {g[x].add(y);in[y]++;}break;}}}}// bfs 寻找拓扑序列StringBuilder ans = new StringBuilder();Queue<Character> q = new LinkedList<Character>();for (char letter: letters) {if (in[letter] == 0) {q.offer(letter);ans.append(letter);}}while (!q.isEmpty()) {char x = q.poll();for (char y: g[x]) {if (--in[y] == 0) {q.offer(y);ans.append(y);}}}return ans.toString();}
}

相关资料

https://oi-wiki.org/graph/topo/

更多关于拓扑排序的题目可见:https://leetcode.cn/tag/topological-sort/problemset/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/6179.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法竞赛入门【码蹄集新手村600题】(MT1020-1040)

算法竞赛入门【码蹄集新手村600题】(MT1020-1040&#xff09; 目录MT1021 %f格式符MT1022 小数、指数MT1023 进制乱炖MT1024 进制形式MT1025 八、十六进制MT1026 合并MT1027 整数逆序MT1028 四位数逆序MT1029 位数MT1030 最大公约数MT1031 最简分数MT1032 最小公倍数MT1033 多项…

Docker 续

Docker 续 一、Docker 网络1.1 Docker 网络实现原理1.2 Docker 的网络模式1.2.1 Docker 网络模式分类 1.3 如何创建各类网络模式1.4 host模式1.5 container模式1.6 none模式1.7 bridge模式1.8 自定义网络 二、资源控制2.1 Cgroup2.2 CPU 资源控制2.2.1 设置CPU使用率上限2.2.2 …

c# Outlook检索设定问题

基于c# 设定outlook约会予定&#xff0c;时间格式是YYYY-MM-DD HH:mm 的情报。 问题发生&#xff1a; 根据开始时间&#xff08;2023/01/01 7:00&#xff09;条件查询该时间是否存在outlook信息时&#xff0c;明明存在一条数据&#xff0c;就是查询不出来数据 c#代码 Strin…

Observability:Synthetic monitoring - 动手实践

在我之前的如下文章里&#xff1a; Observability&#xff1a;Synthetic monitoring - 合成监测入门&#xff08;一&#xff09;&#xff08;二&#xff09; Observability&#xff1a;Synthetic monitoring - 创建浏览器监测&#xff0c;配置单独的浏览器监测器及项目 我详…

数据预处理matlab

matlab数据的获取、预处理、统计、可视化、降维 数据的预处理 - MATLAB & Simulink - MathWorks 中国https://ww2.mathworks.cn/help/matlab/preprocessing-data.html 一、数据的获取 1.1 从Excel中获取 使用readtable() 例1&#xff1a; 使用spreadsheetImportOption…

Vue整体架构分解

Vue.js的整体架构可以分解为以下几个部分: 文章目录 1. 数据驱动2. 组件化3. 响应式系统4. 虚拟DOM5. 插件系统6. 单文件组件7. 模板编译总结 1. 数据驱动 Vue的一个核心特点是数据驱动。Vue会在初始化的时候给数据提供一个observe监听&#xff0c;当数据变化时&#xff0c;会…

uniapp 微信小程序 input详解 带小数点的input、可查看密码的输入框input

官网文档地址 1、template <!-- 本示例未包含完整css&#xff0c;获取外链css请参考上文&#xff0c;在hello uni-app项目中查看 --> <template><view><view class"uni-common-mt"><view class"uni-form-item uni-column"&g…

【C++】开源:跨平台轻量日志库easyloggingpp

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍跨平台轻量日志库easyloggingpp。 无专精则不能成&#xff0c;无涉猎则不能通。。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&am…

详细介绍Matlab中线性规划算法的使用

Matlab中提供了用于线性规划的优化工具箱&#xff0c;其中包含了多种算法&#xff0c;如单纯形法、内点法等。线性规划是一种优化问题&#xff0c;旨在找到一组变量的最佳值&#xff0c;以最大化或最小化线性目标函数&#xff0c;同时满足一组线性约束条件。 下面将详细介绍Ma…

【C++11】智能指针的定义 和 种类 及 使用

智能指针 定义 为什么需要智能指针 在C中&#xff0c;动态分配内存是一项常见的任务&#xff0c;但手动管理分配和释放内存可能会导致很多问题&#xff0c;如内存泄漏、悬垂指针以及多次释放同一块内存等。为了避免这些问题&#xff0c;引入了智能指针的概念&#xff0c;它们…

LiveGBS流媒体平台GB/T28181功能-海康NVR摄像机自带物联网卡摄像头注册GB/T28181国标平台看不到设备的时候如何抓包及排查

海康大华宇视华为等硬件NVR摄像机注册到LiveGBS国标平台看不到设备的时候如何抓包及排查 1、设备注册后查看不到1.1、是否是自带物联网卡的摄像头1.2、关闭萤石云1.3、防火墙排查1.4、端口排查1.5、IP地址排查1.6、设备TCP/IP配置排查1.7、设备多网卡排查1.8、设备接入配置参数…

Docker(四)

文章目录 1. docker其他命令补充2. docker-registry使用3. docker-hub的使用4. 企业级私有仓库harbor4.1 harbor安装4.2 harbor配置https4.3 harbor常见使用4.3.1 harbor新建项目仓库4.3.2 harbor创建用户4.3.3 harbor仓库管理4.3.4 harbor复制管理4.3.5 harbor删除镜像 5. doc…

K8S下如何搭建eureka集群

背景 传统应用上云&#xff0c;基于传统应用需要考虑上云的方案和改造成本&#xff0c;这也是传统应用上云过程中的难点&#xff0c;本篇介绍3台eureka搭建的方案。 方案一 此方案借助了K8S中Service的一些功能。 这种方案是传统方案的简单迁移版本&#xff0c;比较易于理解…

深度学习:tf.keras实现模型搭建、模型训练和预测

在sklearn中&#xff0c;模型都是现成的。tf.Keras是一个神经网络库,我们需要根据数据和标签值构建神经网络。神经网络可以发现特征与标签之间的复杂关系。神经网络是一个高度结构化的图&#xff0c;其中包含一个或多个隐藏层。每个隐藏层都包含一个或多个神经元。神经网络有多…

【微信小程序】使用iView组件库中的icons资源

要在微信小程序中使用iView组件库中的icons资源&#xff0c;需要先下载并引入iView组件库&#xff0c;并按照iView的文档进行配置和使用。 以下是一般的使用步骤&#xff1a; 下载iView组件库的源码或使用npm安装iView。 在小程序项目的app.json文件中添加iView组件库的引入配…

mac端好用的多功能音频软件 AVTouchBar for mac 3.0.7

AVTouchBar是来自触摸栏的视听播放器&#xff0c;将跳动笔记的内容带到触摸栏&#xff0c;触摸栏可显示有趣的音频内容&#xff0c;拥有更多乐趣&#xff0c;以一种有趣的方式播放音乐&#xff0c;该软件支持多种音频播放软件&#xff0c;可在Mac上自动更改音乐~ 音频选择-与内…

Flask Bootstrap 导航条

(43条消息) Flask 导航栏&#xff0c;模版渲染多页面_U盘失踪了的博客-CSDN博客 (43条消息) 学习记录&#xff1a;Bootstrap 导航条示例_bootstrap导航栏案例_U盘失踪了的博客-CSDN博客 1&#xff0c;引用Bootstrap css样式&#xff0c;导航栏页面跳转 2&#xff0c;页面两列…

实验五 分支限界法

实验五 分支限界法 01背包问题的分治限界法的实现 剪枝函数 限界函数 1.实验目的 1、理解分支限界法的剪枝搜索策略&#xff0c;掌握分支限界法的算法框架 2、设计并实现问题&#xff0c;掌握分支限界算法。 2.实验环境 java 3.问题描述 给定n种物品和一背包。物品i的重…

Cesium态势标绘专题-位置点(标绘+编辑)

标绘专题介绍:态势标绘专题介绍_总要学点什么的博客-CSDN博客 入口文件:Cesium态势标绘专题-入口_总要学点什么的博客-CSDN博客 辅助文件:Cesium态势标绘专题-辅助文件_总要学点什么的博客-CSDN博客 本专题没有废话,只有代码,代码中涉及到的引入文件方法,从上面三个链…

企业微信在ios机型无法吊起打开个人信息页接口(openUserProfile)

wx.qy.openUserProfile({type: 1,//1表示该userid是企业成员&#xff0c;2表示该userid是外部联系人userid: "wmEQlEGwAAHxbWYDOK5u3Af13xlYAAAA", //可以是企业成员&#xff0c;也可以是外部联系人success: function(res) {// 回调} });遇到的问题&#xff1a;调用打…