拓扑排序C++

拓扑排序C++

几个基本概念的介绍

入度和出度

图中的度:所谓顶点的度(degree),就是指和该顶点相关联的边数。在有向图中,度又分为入度和出度。

入度 (in-degree) :以某顶点为弧头,终止于该顶点的边的数目称为该顶点的入度

出度 (out-degree) :以某顶点为弧尾,起始于该顶点的弧的数目称为该顶点的出度

邻接表

邻接表,存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。

  • 在有向图中,描述每个点向别的节点连的边(点a->点b这种情况)。

  • 在无向图中,描述每个点所有的边(点a-点b这种情况)

LeetCode习题

207. 课程表

解题思路

本题可约化为: 课程安排图是否是有向无环图(DAG)。即课程间规定了前置条件,但不能构成任何环路,否则课程前置条件将不成立。
思路是通过 拓扑排序 判断此课程安排图是否是有向无环图(DAG) 。

拓扑排序原理: 对 DAG 的顶点进行排序,使得对每一条有向边 (u,v)(u,v)(u,v),均有 uuu(在排序记录中)比 vvv 先出现。亦可理解为对某点 vvv 而言,只有当 vvv 的所有源点均出现了,vvv 才能出现。

通过课程前置条件列表 prerequisites 可以得到课程安排图的邻接表 adjacency,以降低算法时间复杂度,以下两种方法都会用到邻接表。

方法一:入度表(BFS)

本方法中几个数据结构的含义:

  • vector<vector<int>> prerequisites 题目给出参数,其中每个元素 p 是一个依赖关系 p[0] 依赖于 p[1] ,在有向图中,应该是 p[1]->p[0]

  • vector<int> degress 记录所有节点的入度

  • vector<vector<int>> adjacents 邻接表,长度为总课程数,下标 iii 的元素存放所有依赖节点 iii 的节点

  • queue<int> zeros 存放所有目前入度为 0 的顶点

算法流程:

  1. 统计课程安排图中每个节点的入度,生成 入度表 indegrees
  2. 借助一个队列 queue,将所有入度为 0 (没有任何依赖)的节点入队。
  3. queue 非空时,依次将队首节点出队,在课程安排图中删除此节点 pre
    • 并不是真正从邻接表中删除此节点 pre,而是将此节点邻接表对应所有邻接节点 cur,即所有以来该节点的节点的入度 −1,即 indegrees[cur] -= 1
    • 当入度 −1 后邻接节点 cur 的入度为 0,说明 cur 所有的前驱节点(依赖节点)已经被 “删除”,此时将 cur 入队。
  4. 在每次 pre 出队时,执行 numCourses--
    • 若整个课程安排图是有向无环图(即可以安排),则所有节点一定都入队并出队过,即完成拓扑排序。换个角度说,若课程安排图中存在环,一定有节点的入度始终不为 0。
    • 因此,拓扑排序出队次数等于课程个数,返回 numCourses == 0 判断课程是否可以成功安排。
class Solution {
public:bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {vector<int> degrees(numCourses, 0);		// 记录所有顶点的入度,未初始化的为0vector<vector<int>> adjacents(numCourses);	// 邻接表queue<int> zero;	// 零入度的顶点int num = numCourses;for (int i=0; i<prerequisites.size(); ++i) {degrees[prerequisites[i][0]]++;		// 入顶点adjacents[prerequisites[i][1]].push_back(prerequisites[i][0]);		// 出顶点}for (int i=0; i<numCourses; ++i) {if (degrees[i] == 0) {zero.push(i);		// 入度为0的先入队列--num;}}while (!zero.empty()) {int temp = zero.front();zero.pop();for (int j=0; j<adjacents[temp].size(); ++j) {if (--degrees[adjacents[temp][j]] == 0) {zero.push(adjacents[temp][j]);--num;}}}if (num == 0) return true;else return false;}
};

方法二:DFS

原理是通过 DFS 判断图中是否有环。

算法流程:

  1. 借助一个标志列表 flag,用于判断每个节点 i (课程)的状态:
    1. 未被 DFS 访问:i == 0
    2. 已被其他节点启动的 DFS 访问:i == -1
    3. 已被当前节点启动的 DFS 访问:i == 1
  2. numCourses 个节点依次执行 DFS,判断每个节点起步 DFS 是否存在环,若存在环直接返回 False。DFS 流程:
    • 终止条件:
      1. flag[i] == -1,说明当前访问节点已被其他节点启动的 DFS 访问,无需再重复搜索,直接返回 True。
      2. flag[i] == 1,说明在本轮 DFS 搜索中节点 i 被第 2 次访问,即 课程安排图有环 ,直接返回 False。
    • 将当前访问节点 i 对应 flag[i] 置 1,即标记其被本轮 DFS 访问过;
    • 递归访问当前节点 i 的所有邻接节点 j,当发现环直接返回 False;
    • 当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点 flag 置为 −1 并返回 True。
  3. 若整个图 DFS 结束并未发现环,返回 True。
class Solution {
public:bool dfs(vector<vector<int>>& adjacents, vector<int>& flags, int curr) {if (flags[curr] == 1) return false;else if (flags[curr] == -1) return true;flags[curr] = 1;for (int i=0; i<adjacents[curr].size(); ++i) {if (!dfs(adjacents, flags, adjacents[curr][i])) return false;}flags[curr] = -1;return true;}bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {vector<vector<int>> adjacents(numCourses);vector<int> flags(numCourses, 0);for (vector<int> p: prerequisites) {adjacents[p[1]].push_back(p[0]);}for (int i=0; i<numCourses; ++i) {if (!dfs(adjacents, flags, i))  return false;}return true;}
};

210. 课程表 II

与上题思路一致

BFS

class Solution {
private:// 存储有向图vector<vector<int>> edges;// 存储每个节点的入度vector<int> indeg;// 存储答案vector<int> result;public:vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {edges.resize(numCourses);indeg.resize(numCourses);for (const auto& info: prerequisites) {edges[info[1]].push_back(info[0]);++indeg[info[0]];}queue<int> q;// 将所有入度为 0 的节点放入队列中for (int i = 0; i < numCourses; ++i) {if (indeg[i] == 0) {q.push(i);}}while (!q.empty()) {// 从队首取出一个节点int u = q.front();q.pop();// 放入答案中result.push_back(u);for (int v: edges[u]) {--indeg[v];// 如果相邻节点 v 的入度为 0,就可以选 v 对应的课程了if (indeg[v] == 0) {q.push(v);}}}if (result.size() != numCourses) {return {};}return result;}
};

DFS

class Solution {
private:vector<vector<int>> edges;vector<int> visited;bool valid = true;stack<int> S;void dfs(int u) {visited[u] = 1;for (int v : edges[u]) {if ( visited[v] == 1) {valid = false;return;}else if (visited[v] == 0) {dfs(v);if (!valid) return;}}visited[u] = 2;S.push(u);}public:vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {edges.resize(numCourses);visited.resize(numCourses);for (const auto& v : prerequisites) {edges[v[1]].push_back(v[0]);}for (int i=0; i<numCourses && valid; i++) {if (!visited[i]) dfs(i);}if (!valid) return {};else {vector<int> res;while (!S.empty()) {res.push_back(S.top());S.pop();}return res;}

解题思路参考:https://leetcode-cn.com/problems/course-schedule/solution/course-schedule-tuo-bu-pai-xu-bfsdfsliang-chong-fa/

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

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

相关文章

C++面试常考题——编译内存相关

C面试常考题——编译内存相关 转自&#xff1a;https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/e4ns5g/ C程序编译过程 编译过程分为四个过程&#xff1a;编译&#xff08;编译预处理、编译、优化&#xff09;&#xff0c;汇编&#xff0c;链接。 编译预处…

C++遍历删除元素

C遍历删除元素 转自&#xff1a;http://zencoder.info/2019/10/11/erase-element-from-container/ 今天看到一个patch fix从std::map中遍历删除元素导致crash问题&#xff0c;突然意识到自己对如何正确地从map等C容器中删除元素也没有很牢固清醒的认知。重新梳理了下这块的正…

关键字库函数

关键字库函数 转自&#xff1a;https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/ej3mx1/ sizeof和strlen的区别 strlen 是头文件<cstring> 中的函数&#xff0c;sizeof 是 C 中的运算符。 strlen 测量的是字符串的实际长度&#xff08;其源代码如下&…

memcpy和memmove的区别以及内存重叠问题

memcpy和memmove的区别以及内存重叠问题 转自&#xff1a;https://www.codecomeon.com/posts/89/ 区别 memcpy() 和 memmove() 都是C语言中的库函数&#xff0c;在头文件 string.h 中&#xff0c;作用是拷贝一定长度的内存的内容&#xff0c;原型分别如下&#xff1a; void…

从头搭建一个深度学习框架

从头搭建一个深度学习框架 转自&#xff1a;Build a Deep Learning Framework From Scratch 代码&#xff1a;https://github.com/borgwang/tinynn 当前深度学习框架越来越成熟&#xff0c;对于使用者而言封装程度越来越高&#xff0c;好处就是现在可以非常快速地将这些框架作为…

关于python import的sys.path路径问题

关于python import的sys.path路径问题 sys.path 先说一下 sys.path 这个变量&#xff0c;该变量需要导入 sys 官方库方可使用&#xff0c;它是一个列表&#xff0c;是当前 python 文件 import 库时会逐个搜索列表中的路径。 初始化 sys.path 从这些位置初始化&#xff1a; …

python pdb调试基本命令整理

python pdb调试基本命令整理 使用简介 启动调试 侵入式 在 py 文件内部设置&#xff1a; import pdb; pdb.set_trace()程序会在运行到这一行时停下来&#xff0c;进入 pdb 交互。 非侵入式 在运行 py 脚本时&#xff1a; python -m pdb main.py程序会在一启动时就进入 pdb 交…

Docker概念理解

Docker概念理解 本文非Docker命令大全&#xff0c;而是对Docker的概念、原理等作说明&#xff0c;适合有一定实操经验后来加深理解。 转自&#xff1a;docker从入门到实践 Docker简介 本章将带领你进入 Docker 的世界。 什么是 Docker&#xff1f; 用它会带来什么样的好处&a…

Dockerfile详解

Dockerfile详解 转自&#xff1a;https://yeasy.gitbook.io/docker_practice/ 使用Dockerfile定制镜像 从刚才的 docker commit 的学习中&#xff0c;我们可以了解到&#xff0c;镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操…

Dockerfile最佳实践

Dockerfile最佳实践 本文是原作者对 Docker 官方文档中 Best practices for writing Dockerfiles 的理解与翻译。 转自&#xff1a;附录四&#xff1a;Dockerfile 最佳实践 一般性指南和建议 容器应该是短暂的 通过 Dockerfile 构建的镜像所启动的容器应该尽可能短暂&#xf…

Linux内存背后的那些神秘往事

Linux内存背后的那些神秘往事 作者&#xff1a;大白斯基&#xff08;公众号&#xff1a;后端研究所&#xff09; 转自&#xff1a;https://mp.weixin.qq.com/s/l_YdpyHht5Ayvrc7LFZNIA 前言 大家好&#xff0c;我的朋友们&#xff01; CPU、IO、磁盘、内存可以说是影响计算机…

mmdeploy快速上手

mmdeploy快速上手 若要将使用 openmmlab 的框架&#xff08;如mmdet、mmcls&#xff09;等训练的模型进行快速部署&#xff0c;同样来自 openmmlab 的 mmdeploy 无疑是最合适的选择&#xff0c;本文将简单地完成一个 Faster RCNN 模型的部署。 配置 本文基于如下软硬件配置&…

精简CUDA教程——CUDA Driver API

精简CUDA教程——CUDA Driver API tensorRT从零起步迈向高性能工业级部署&#xff08;就业导向&#xff09; 课程笔记&#xff0c;讲师讲的不错&#xff0c;可以去看原视频支持下。 Driver API概述 CUDA 的多级 API CUDA 的 API 有多级&#xff08;下图&#xff09;&#xff…

CUDA编程入门极简教程

CUDA编程入门极简教程 转自&#xff1a;CUDA编程入门极简教程 作者&#xff1a;小小将 前言 2006年&#xff0c;NVIDIA公司发布了CUDA&#xff0c;CUDA是建立在NVIDIA的CPUs上的一个通用并行计算平台和编程模型&#xff0c;基于CUDA编程可以利用GPUs的并行计算引擎来更加高效地…

精简CUDA教程——CUDA Runtime API

精简CUDA教程——CUDA Runtime API tensorRT从零起步迈向高性能工业级部署&#xff08;就业导向&#xff09; 课程笔记&#xff0c;讲师讲的不错&#xff0c;可以去看原视频支持下。 Runtime API 概述 环境 图中可以看到&#xff0c;Runtime API 是基于 Driver API 之上开发的…

Python并发——concurrent.futures梳理

Python并发——concurrent.futures梳理 参考官方文档&#xff1a; concurrent.futures — 启动并行任务 Executor对象 class concurrent.funtures.Executor该抽象类是 ThreadPoolExecutor 和 ProcessPoolExecutor 的父类&#xff0c;提供异步执行调用方法。要通过它的子类调用…

TensorRT ONNX 基础

TensorRT ONNX 基础 tensorRT从零起步迈向高性能工业级部署&#xff08;就业导向&#xff09; 课程笔记&#xff0c;讲师讲的不错&#xff0c;可以去看原视频支持下。 概述 TensorRT 的核心在于对模型算子的优化&#xff08;合并算子、利用当前 GPU 特性选择特定的核函数等多种…

回文子串、回文子序列相关题目

回文子串、回文子序列相关题目 回文子串是要连续的&#xff0c;回文子序列可不是连续的。 516. 最长回文子序列 dp数组含义&#xff1a;dp[i][j]dp[i][j]dp[i][j] 表示子序列 s[i,j]s[i,j]s[i,j] 中的最长回文子序列的长度。 dp数组初始化&#xff1a;子序列长度为 1 时&am…

mmdetection tools工具梳理

mmdetection tools工具梳理 mmdetection 是一个非常好用的开源目标检测框架&#xff0c;我们可以用它方便地训练自己的目标检测模型&#xff0c;mmdetection 项目仓库提供许多实用的工具来实现帮助我们进行各种测试。本篇将梳理以下 mmdetection 项目仓库 tools 目录下的各种实…

TensorRT ONNX 基础(续)

TensorRT ONNX 基础&#xff08;续&#xff09; PyTorch正确导出ONNX 几条推荐的原则&#xff0c;可以减少潜在的错误&#xff1a; 对于任何使用到 shape、size 返回值的参数时&#xff0c;例如 tensor.view(tensor.size(0), -1) 这类操作&#xff0c;避免直接使用 tensor.s…