leetcode 所有可能的路径(图的遍历:深度优先和广度优先)

 leetcode 链接:

 所有可能的路径

1 图的基本概念

1.1 有向图和无向图

左边是有向图,右边是无向图。对于无向图来说,图中的边没有方向,两个节点之间只可能存在一条边,比如 0 和 1 之间的边,因为是无向图,这条边可以表示从 0 到 1 的边,也可以表示从 1 到 0 的边。对于有向图来说,图中的边有方向, 0 和 1 之间可以存在两条边,一条表示从 0 到 1 的边,一条表示从 1 到 0 的边。

用邻接矩阵来表示上边两个图,如下所示。

// 有向图
[[1], // 有从 0 到 1 的边[0, 2], // 从 1 到 0 的边和从 1 到 2 的边[0]  // 从 2 到 0 的边
]// 无向图
[[1, 2], // 从 0 到 1 和从 0 到 2 的边[0, 2], // 从 1 到 0 和从 1 到 2 的边[0, 1]  // 从 2 到 0 和从 2 到 1 的边
]

1.2 有环图和无环图

讨论有环图还是无环图,一般说的是有向图。因为对于无向图来说,从 0 到 1 有边,那么从 1 到 0 就有边,本身就是一个环。

有环图说的是从一个节点开始遍历,在遍历过程中还能遍历到这个节点的图,除了开始节点和结束节点是相同的,其它节点不能重复出现,并且路径长度大于 2。

在图的遍历算法中,为了防止一个节点被重复遍历,往往需要一个 visited[n] 数组来标记一个节点是不是被遍历过。visited 数组下标是节点的值,元素值均被初始化为 0,当一个节点被遍历时,则将值改为 1。对于有向有环图来说,需要 visited 数组来标记,因为有环,一个节点可能被多次访问;对于有向无环图来说,一个节点不会被重复遍历,所以不需要 visited 数组来标记。

1.3 连通图和非连通图

下边两个图,上边的是非连通图,下边的是连通图。连通图,指的是从一个节点出发沿着边进行遍历,能把图中的节点都遍历到的图。 这个很像那种益智小游戏,看怎么样能一笔把图中的所有点连起来。

当对图做遍历时,如果图是连通的,那么从一个节点开始,遍历一次,就能将图中所有的节点遍历一遍,所以遍历一次就可以了。如果图不是连通的,那么遍历一次,无法将所有的点都遍历一遍,这个时候要从每个点都开始,每个节点都要开始一次,遍历一遍。

2 深度优先搜索和广度优先搜索

图是一种二维的数据结构,可以使用邻接表或者邻接矩阵来表示,更多的使用邻接表来表示,有行和列。

1.3 节中连通图的邻接矩阵表示如下:

深度优先,从遍历过程来看,优先在纵向遍历,纵深,深度。

广度优先,从遍历过程来看,优先在横向遍历,横向是广度。

纵向是深度,横向是广度。

上边这个图,从节点 0 开始遍历,第一步遍历到节点 1,下一步遍历的选择就是深度优先和广度优先的区别。下一步遍历 1 节点开始的链表,也就是跳到第二行开始遍历,遍历到节点 1 的邻接点 2,这就是深度优先遍历;下一步还是在 0 这一行,遍历节点 2,这就是广度优先遍历。

深度优先遍历和广度优先遍历不仅仅适用于图这种数据结构。二叉树的遍历包括前序遍历,中序遍历,后序遍历以及层序遍历。前 3 种遍历方式属于深度优先遍历,层序遍历属于广度优先遍历。二叉树也属于二维的数据结构。

二叉树遍历算法和应用

2.1 时间复杂度

深度优先和广度优先遍历图,都是沿着边进行遍历的。如果节点个数是 n,那么对于无向图来说,边的个数最大是 n(n - 1)/2;对有向图来说,边的个数最大是 n(n - 1)。

所以最差的情况下,两种算法的时间复杂度均是 O(n * n)。

3 所有可能的路径

如下是 leetcode 中的题目说明。

题意中的图是有向无环图,有向并且没有环,所以在遍历的时候不需要使用 visited 数据来标记一个节点是不是被访问过,因为没有环,一个节点不会被重复访问。

3.1 深度优先

这个题目适合使用深度优先遍历算法。深度优先遍历,是递归算法,递归算法需要注意两点:递归退出条件,一定要有退出条件,否则会一直递归下去;递归体,也就是递归算法的主要逻辑。递归算法的这两点与循环类似,循环算法也包括循环退出条件和循环主要逻辑。

找路径的题目,使用递归算法的题目,不管是图和是二叉树,最核心的就是如下代码注释中的三段式。

(1)将节点加入到路径

(2)递归

(3)将节点移出路径

为什么加入的节点要移出呢,因为这个节点加入之后,进行了递归运算。也就是说以这个节点为基础的路径都已经全部遍历。下一次要遍历的节点与这个节点是并列的节点,并不是路径的前后关系,它们属于这一行的邻接点。要进行下一步遍历,这个节点需要移出,因为后边遍历的节点跟这个节点不在一个路径,只不过都是当前这一行的得邻接点而已。

class Solution {
public:vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {const int n = graph.size();// 没有数据,直接返回if (n == 0) {return ret;}// 要找的路径是从 o 到 n - 1// 所以从 0 开始遍历// 在遍历之前要把这个点加入到路径中one_path.push_back(0);// 深度优先遍历DfsScan(graph, 0, n);return ret;}// 深度优先遍历时一种递归算法void DfsScan(vector<vector<int>>& graph, int index, const int n) {// 递归退出条件,当前这个点是 n - 1 的时候,说明找到了这样一个路径// 将这条路径加入到结果中if (index == n - 1) {ret.push_back(one_path);return;}// 递归体,深度优先搜索// 对于遍历的这个节点,找到这个节点锁代表的这一行,遍历这一行// 这种方式取出数据的一行,会发生拷贝,直接使用引用来获取数据可以避免数据拷贝//   vector<int> line = graph[index];//   int line_size = line.size();//   for (int i = 0; i < line_size; i++) {for (int & data : graph[index]) {// 三段式// 1、push_back 将节点加入到路径中// 2、递归运算// 3、将节点从路径中移走one_path.push_back(data);DfsScan(graph, data, n);one_path.pop_back();}}private:vector<vector<int>> ret;vector<int> one_path;
};

3.2 广度优先

广度优先需要使用一个队列和循环算法。在循环之前将一个元素加入到队列中,循环的条件是队列不为空,循环中的逻辑是把新遍历到的节点加入到队列中。

找路径这样的题目,广度优先没有深度优先好理解。深度优先在遍历的过程中,前后相邻的两个被遍历的点一定是一条路径上的前后的两个点,这样遍历到一个点,直接追加到路径上就可以。而广度优先遍历不是这样的,相邻两次遍历的点不是属于路径上的前后点,而是只属于当前这一行首节点的邻接点。这样就需要给每个节点维护一个路径,当这个节点是首节点的时候,那么这个节点的所有邻接点的路径都要更新,都要在这个首节点的路径的基础上加上当前这个节点。

  class Solution {
public:struct Node {int data;vector<int> path;};vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {int size = graph.size();if (size == 0) {return ret;}Node node;node.data = 0;// 将节点加入到队列中// 节点保存着到当前这个节点的路径node.path.push_back(0);// 循环的出发条件,只要队列不是空,循环就继续进行q.push(node);while (!q.empty()) {// 从队列中获取一个元素// 开始遍历以这个元素为行首的节点,即广度优先遍历Node tmp = q.front();q.pop();for (int &d : graph[tmp.data]) {Node linenode;linenode.data = d;linenode.path = tmp.path;linenode.path.push_back(d);// 当前这个节点是 size - 1 了,找到了满足条件的一个路径// 那么这个节点就不需要加入队列了// 加入队列之后下次遍历的时候,路径后边还要加入其它节点// 继续追加没有意义,因为当前已经是要找的路径了if (d == size - 1) {ret.push_back(linenode.path);} else {q.push(linenode);}}}return ret;}private:std::queue<Node> q;vector<vector<int>> ret;
};

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

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

相关文章

线性代数|机器学习-P12Ax=b条件下x最小值问题

文章目录 1. Axb下的最值问题-图形转换2. Gram-Schmidt 标准形3. 迭代法-Krylov子空间法 1. Axb下的最值问题-图形转换 假设我们有一个直线方程如下&#xff1a; 3 x 1 4 x 2 1 \begin{equation} 3x_14x_21 \end{equation} 3x1​4x2​1​​ 在二维平面上&#xff0c;各个范…

DDMA信号处理以及数据处理的流程---原始数据生成

Hello&#xff0c;大家好&#xff0c;我是Xiaojie&#xff0c;好久不见&#xff0c;欢迎大家能够和Xiaojie一起学习毫米波雷达知识&#xff0c;Xiaojie准备连载一个系列的文章—DDMA信号处理以及数据处理的流程&#xff0c;本系列文章将从目标生成、信号仿真、测距、测速、cfar…

LLVM 后端执行流程

异构计算程序工作流程 图4-1中的LLVM后端的主要功能是代码生成&#xff0c;其中包括若干指令生成分析转换pass&#xff0c;将LLVM IR 转换为特定目标架构的机器代码 LLVM 流水线结构 输入指令经过图4-2中的各个阶段&#xff0c;从最初的LLVM IR&#xff0c;逐步演化为Selectio…

管理数据必备;侦听器watch用法详解,vue2与vue3中watch的变化与差异

目录 一、侦听器&#xff08;watch&#xff09;是什么&#xff1f; 二、Vue2中的watch&#xff08;Options API&#xff09; 2.1、函数式写法 2.2、对象式写法 ①对象式基础写法 ②回调函数handler ③deep属性 ④immediate属性 三、Vue3中的watch 3.1、向下兼容&#xff…

两句话让LLM逻辑推理瞬间崩溃!!

一道简单的逻辑问题&#xff0c;竟让几乎所有的LLM全军覆没&#xff1f; 对于人类来说&#xff0c;这个名为「爱丽丝梦游仙境」&#xff08;AIW&#xff09;的测试并不算很难—— 「爱丽丝有N个兄弟&#xff0c;她还有M个姐妹。爱丽丝的兄弟有多少个姐妹&#xff1f;」 稍加思考…

LeetCode1143最长公共子序列

题目描述 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些字符&#xff08…

redis windos修复版本

遇到的问题: Django的channel插件连接安装在windows上的redis报错: unknown command BZPOPMIN, channels-redis版本和redis不兼容导致.解决方案: 更新Redis版本. 微软官方维护的 Redishttps://github.com/microsoftarchive/redis/releases 2016年后就不更新了, 版本停留在了3.x…

学生信息管理(C语言)

学生信息管理 (1)问题描述 学生信息包括:学号,姓名,年龄,性别,出生年月,地址,电话,E-mail等。试设计一学生信息管理系统,使之能提供以下功能: 系统以菜单方式工作学生信息录入功能(学生信息用文件保存)---输入学生信息浏览功能---输出查询、排序功能---算法1、…

前后端实现文件上传进度条-实时进度

后端接口代码&#xff1a; PostMapping("/upload")public ResponseEntity<String> handleFileUpload(RequestParam("file") MultipartFile file) {try {// 获取文件名String fileName file.getOriginalFilename();// 创建上传目标路径Path targetPa…

CentOS7 MySQL5.7.35主从 不停机搭建 以及配置

如需安装MySQL&#xff0c;参照MySQL 5.7.35 安装教程 https://blog.csdn.net/CsethCRM/article/details/119418841一、主&从 环境信息准备 1.1.查看硬盘信息&#xff0c;确保磁盘够用&#xff08;主&从&#xff09; df -h1.2.查看内存信息 &#xff08;主&从&am…

C++ 贪心算法——跳跃游戏、划分字母区间

一&#xff1a;跳跃游戏 55. 跳跃游戏 题目描述&#xff1a;给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1…

解决阿里云的端口添加安全组仍然无法扫描到

发现用线上的网站扫不到这个端口&#xff0c;这个端口关了&#xff0c;但是没有更详细信息了 我用nmap扫了一下我的这个端口&#xff0c;发现主机是活跃的&#xff0c;但是有防火墙&#xff0c;我们列出云服务器上面的这个防火墙list&#xff0c;发现确实没有5566端口 参考&a…

Mac环境下,简单反编译APK

一、下载jadx包 https://github.com/skylot/jadx/releases/tag/v1.4.7 下载里面的这个&#xff1a;下载后&#xff0c;找个干净的目录解压&#xff0c;我是放在Downloads下面 二、安装及启动 下载和解压 jadx&#xff1a; 下载 jadx-1.4.7.zip 压缩包。将其解压到你希望的目…

内网安全--隧道技术代理技术

注:本文仅做技术交流,请勿非法破坏... 目录 项目: 1-Ngrok 用法 2-Frp 用法 3-Nps 用法 4-Spp 用法 工具: windows下: Proxifier(推荐~) Sockscap ccproxy Linux下: Proxychains 用法 http://t.csdnimg.cn/88Ew7 隧道技术&#xff1a;解决不出网协议上线的问…

**《Linux/Unix系统编程手册》读书笔记24章**

D 24章 进程的创建 425 24.1 fork()、exit()、wait()以及execve()的简介 425 . 系统调用fork()允许父进程创建子进程 . 库函数exit(status)终止进程&#xff0c;将进程占用的所有资源归还内核&#xff0c;交其进行再次分配。库函数exit()位于系统调用_exit()之上。在调用fo…

“RabbitMQ入门指南:从入门到起飞,这一篇就够!打造高效消息通信系统的第一步“。

1.前言 RabbitMQ是一个开源的消息代理软件&#xff0c;它实现了高级消息队列协议&#xff08;AMQP&#xff09;的标准&#xff0c;并用Erlang语言编写。作为消息代理&#xff0c;RabbitMQ接收、存储和转发消息&#xff0c;帮助应用程序之间实现异步通信。它提供了一个强大而灵活…

Qt开发技术:Q3D图表开发笔记(四):Q3DSurface三维曲面图颜色样式详解、Demo以及代码详解

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/139424086 各位读者&#xff0c;知识无穷而人力有穷&#xff0c;要么改需求&#xff0c;要么找专业人士&#xff0c;要么自己研究 红胖子网络科技博…

PawSQL优化 | 分页查询太慢?别忘了投影下推

​在进行数据库应用开发中&#xff0c;分页查询是一项非常常见而又至关重要的任务。但你是否曾因为需要获取总记录数的性能而感到头疼&#xff1f;现在&#xff0c;让PawSQL的投影下推优化来帮你轻松解决这一问题&#xff01;本文以TPCH的Q12为案例进行验证&#xff0c;经过Paw…

Redisson分布式锁原理解析

前言 首先Redis执行命令是单线程的&#xff0c;所以可以利用Redis实现分布式锁&#xff0c;而对于Redis单线程的问题&#xff0c;是其线程模型的问题&#xff0c;本篇重点是对目前流行的工具Redisson怎么去实现的分布式锁进行深入理解&#xff1b;开始之前&#xff0c;我们可以…

Vmess协议是什么意思? VLESS与VMess有什么区别?

VMess 是一个基于 TCP 的加密传输协议&#xff0c;所有数据使用 TCP 传输&#xff0c;是由 V2Ray 原创并使用于 V2Ray 的加密传输协议&#xff0c;它分为入站和出站两部分&#xff0c;其作用是帮助客户端跟服务器之间建立通信。在 V2Ray 上客户端与服务器的通信主要是通过 VMes…