代码随想录算法训练营第60期第五十三天打卡

news2025/6/2 10:09:30

        大家好,我们今天来到了最后一章图论,其实图论比较难,涉及的算法也比较多,今天比较重要的就是深度优先搜索与广度优先搜索,后面的迪杰斯特拉算法等算法在我们求最短路都会涉及到,还有最近公共祖先,并查集等问题后面都会讲到,我们今天主要就看深搜与广搜的理论基础并且用这两种算法解决一道所有可达路径的问题。

第一部分深度优先搜索理论基础

        其实我们主要会涉及两种搜索算法,深搜(dfs)与广搜(bfs),我们首先要区分这两种算法,我们就先看看区别在哪里,首先告诉大家dfs是可一个方向去搜,不到黄河不回头,直到遇到绝境了,搜不下去了,再换方向(换方向的过程就涉及到了回溯)。其实我比较喜欢使用深搜,当然不排除有的题目只能使用广搜才可以解决,印象中我们代码随想录的后面就有一道叫做字符串接龙的问题,那道题目似乎就只能使用广度优先搜索解决,而bfs是先把本节点所连接的所有节点遍历一遍,走到下一个节点的时候,再把连接节点的所有节点遍历一遍,搜索方向更像是广度,四面八方的搜索过程。

        其实在图上可以更加直观地显示两种搜索算法的区别:

       如果我们要搜索从起点到终点的所有路径,我们就先找到目的地6随后回溯更换方向,具体的路径大家可以去代码随想录网站去看,我就不再展示了,其实大家理解就是一条路走到黑,知道遇到终止条件。

       接下来我们就一起看看深搜三部曲,第一步确认递归函数,参数,通常我们递归的时候,我们递归搜索需要了解哪些参数,其实也可以在写递归函数的时候,发现需要什么参数,再去补充就可以。一般情况,深搜需要 二维数组数组结构保存所有路径,需要一维数组保存单一路径,这种保存结果的数组,我们可以定义一个全局变量,避免让我们的函数参数过多。其实我们今天的题目就有这种使用二维数组来保存路径的题目,第二步确认终止条件,终止条件很重要,很多同学写dfs的时候,之所以容易死循环,栈溢出等等这些问题,都是因为终止条件没有想清楚。我们一般写深搜的时候我们一般会先写终止条件,终止添加不仅是结束本层递归,同时也是我们收获结果的时候。我们这时候其实就是收获路径的时候,如果说我们使用二维数组来存储路径那么这个时候我们就可以找到一条完整的路径,我们就把这一条路径添加到二维数组里,第三步处理目前搜索节点出发的路径,这个或许就是深搜地精华,每一道题目都有特色,后面我们会讲解很多岛屿类的问题我们就会发现每一个题目都会有特色。

       关于深搜理论基础我大概就讲解这么多,剩下的我们就借助题目来理解。接下来我们看广搜的理论基础。

第二部分广度优先搜索理论基础

        广搜(bfs)是一圈一圈的搜索过程,和深搜(dfs)是一条路跑到黑然后再回溯。其实在树里面,我们对二叉树比较了解,其实广搜大家可以理解为层序遍历,因为广搜是从起点出发,以起始点为中心一圈一圈进行搜索,一旦遇到终点,记录之前走过的节点就是一条最短路。这一点很重要,只要我们使用广搜搜索到的路径就一定会是最短路径,然,也有一些问题是广搜 和 深搜都可以解决的,例如岛屿问题,这类问题的特征就是不涉及具体的遍历方式,只要能把相邻且相同属性的节点标记上就行

        其实如实告诉大家,我一般不适用广搜,我比较喜欢使用深搜,但是广搜我也必须要会,我们具体来看看广搜是如何解决具体题目的,我们就直接看看代码框架,其实我还是把示意图放到下面:

       其实大家可以很清楚看到我们是一圈一圈来搜索的,接下来我们来看代码框架,我们可以考虑使用队列,因为考虑到队列先进先出的特性我们不需要一圈逆时针一圈顺时针,如果我们使用栈的话,我们就必须一圈顺时针一圈逆时针,有时候很容易出错,

int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 表示四个方向
// grid 是地图,也就是一个二维数组
// visited标记访问过的节点,不要重复访问
// x,y 表示开始搜索节点的下标
void bfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
    queue<pair<int, int>> que; // 定义队列
    que.push({x, y}); // 起始节点加入队列
    visited[x][y] = true; // 只要加入队列,立刻标记为访问过的节点
    while(!que.empty()) { // 开始遍历队列里的元素
        pair<int ,int> cur = que.front(); que.pop(); // 从队列取元素
        int curx = cur.first;
        int cury = cur.second; // 当前节点坐标
        for (int i = 0; i < 4; i++) { // 开始想当前节点的四个方向左右上下去遍历
            int nextx = curx + dir[i][0];
            int nexty = cury + dir[i][1]; // 获取周边四个方向的坐标
            if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;  // 坐标越界了,直接跳过
            if (!visited[nextx][nexty]) { // 如果节点没被访问过
                que.push({nextx, nexty});  // 队列添加该节点为下一轮要遍历的节点
                visited[nextx][nexty] = true; // 只要加入队列立刻标记,避免重复访问
            }
        }
    }

}

       上面是一个代码模板,我后面会给大家重点讲解,我们是使用队列来实现。大家先做了解,我们接下来就看一道具体的题目,我先使用深搜给大家解释一遍后面的题目有的我会使用广搜。

第三部分例题对应卡码网编号98的所有可达路径

        废话不多说,我们就直接看看题目要求:

       我们就是搜索可达路径,其实我们也很清楚一点这道题目我们是规定好了边,其实我们就需要存图,这里我先告诉大家存图的话主要有两种方式,一种是邻接表一种是邻接矩阵,我们这里就使用邻接矩阵来存储,我们就先使用深搜来解决,我们是从节点1出发最后到节点n,我们要找到所有的可达路径,我们就需要一个一维数组来存储我们当前搜索到的路径,我们使用一个二维数组来存储我们搜索好的路径,我们到达终止条件的话就到了我们收获路径的时候,我就直接先给出大家深搜的解题代码,对了大家注意我们以后图论的题目大多使用ACM模式我们要注意输出格式,这里与力扣不一样了,力扣只需要我们写出核心代码,而我们在这里要写出完整的代码:

#include <iostream>
#include <vector>

using namespace std;

vector<vector<int>> result;
vector<int> path;

void dfs(const vector<vector<int>> &grid, int x, int n)
{
    if (x == n)
    {
        result.push_back(path);
        return;
    }
    for (int i = 1; i <= n; ++i)
    {
        if (grid[x][i] == 1)
        {
            path.push_back(i);
            dfs(grid, i, n);
            path.pop_back();
        }
    }
}

int main()
{
    int n,m; cin >> n >> m;
    vector<vector<int>> grid(n + 1, vector<int>(n + 1, 0));
    while(m--)
    {
        int s, t; cin >> s >> t;
        grid[s][t] = 1;
    }
    path.push_back(1);
    dfs(grid, 1, n);
    if (result.size() == 0) cout << -1 << '\n';
    for (const vector<int> &pa: result)
    {
        for (int i = 0; i < pa.size(); ++i)
        {
            if (i > 0) cout << " ";
            cout << pa[i];
        }
        cout << '\n';
    }
    return 0;
}

        接下来我们使用邻接表来解决这道题目:

#include <iostream>
#include <vector>
#include <list>
using namespace std;

vector<vector<int>> result; // 收集符合条件的路径
vector<int> path; // 1节点到终点的路径

void dfs (const vector<list<int>>& graph, int x, int n) {

    if (x == n) { // 找到符合条件的一条路径
        result.push_back(path);
        return;
    }
    for (int i : graph[x]) { // 找到 x指向的节点
        path.push_back(i); // 遍历到的节点加入到路径中来
        dfs(graph, i, n); // 进入下一层递归
        path.pop_back(); // 回溯,撤销本节点
    }
}

int main() {
    int n, m, s, t;
    cin >> n >> m;

    // 节点编号从1到n,所以申请 n+1 这么大的数组
    vector<list<int>> graph(n + 1); // 邻接表
    while (m--) {
        cin >> s >> t;
        // 使用邻接表 ,表示 s -> t 是相连的
        graph[s].push_back(t);

    }

    path.push_back(1); // 无论什么路径已经是从0节点出发
    dfs(graph, 1, n); // 开始遍历

    // 输出结果
    if (result.size() == 0) cout << -1 << endl;
    for (const vector<int> &pa : result) {
        for (int i = 0; i < pa.size() - 1; i++) {
            cout << pa[i] << " ";
        }
        cout << pa[pa.size() - 1]  << endl;
    }
}

       其实这道题目不适合广搜来实现,后面的题目我会考虑使用广搜实现,广搜主要是解决最短路径的问题,这道题目我就分享到这里。

今日总结

      今天我们初次接触了深搜与广搜,我们大致了解了这两宗搜索算法的不同点,我们尝试使用深搜来解决所有可达路径的问题,大家多看看就可以,这个并不难理解,我们下次再见!最后祝大家端午节快乐!

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

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

相关文章

Nacos实战——动态 IP 黑名单过滤

1、需求分析 一些恶意用户&#xff08;‏可能是黑客、爬虫、DDoS ؜攻击者&#xff09;可能频繁请求服务器资​源&#xff0c;导致资源占用过高。针对这种问题&#xff0c;可以通过IP‏ 封禁&#xff0c;可以有效拉؜黑攻击者&#xff0c;防止资源​被滥用&#xff0c;保障合法…

实验设计与分析(第6版,Montgomery)第5章析因设计引导5.7节思考题5.14 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第5章析因设计引导5.7节思考题5.14 R语言解题。主要涉及方差分析&#xff0c;正态假设检验&#xff0c;残差分析&#xff0c;交互作用图。 dataframe<-data.frame( strengthc(9.60,9.…

在Ubuntu20.04上安装ROS Noetic

本章教程,主要记录在Ubuntu20.04上安装ROS Noetic。 一、添加软件源 sudo sh -c . /etc/lsb-release && echo "deb http://mirrors.tuna.tsinghua.edu.cn/ros/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/ros-latest.list二、设置秘钥 …

python里面导入yfinance的时候报错

我的代码&#xff1a; import yfinance as yf import os proxy http://127.0.0.1:7890 # 代理设置&#xff0c;此处修改 os.environ[HTTP_PROXY] proxy os.environ[HTTPS_PROXY] proxydata yf.download("AAPL",start"2010-1-1",end"2021-8-1&quo…

winform LiveCharts2的使用--图表的使用

介绍 对于图标&#xff0c;需要使用到livechart2中的CartesianChart 控件&#xff0c;是一个“即用型”控件&#xff0c;用于使用笛卡尔坐标系创建绘图。需要将Series属性分配一组ICartesianSeries。 例如下面代码&#xff0c;创建一个最简单的图表&#xff1a; cartesianCha…

【计算机网络】IPv6和NAT网络地址转换

IPv6 IPv6协议使用由单/双冒号分隔一组数字和字母&#xff0c;例如2001:0db8:85a3:0000:0000:8a2e:0370:7334&#xff0c;分成8段。IPv6 使用 128 位互联网地址&#xff0c;有 2 128 2^{128} 2128个IP地址无状态地址自动配置&#xff0c;主机可以通过接口标识和网络前缀生成全…

flutter简单自定义跟随手指滑动的横向指示器

ScrollController _scrollController ScrollController();double _scrollIndicatorWidth 60.w;//指示器的长度double _maxScrollPaddingValue 30.w;//指示器中蓝条可移动的最大距离double _scrollPaddingValue 0.0;//指示器中蓝条左边距(蓝条移动距离)overridevoid initSta…

有机黑鸡蛋与普通鸡蛋:差异剖析与选购指南

在我们的日常饮食结构里&#xff0c;鸡蛋始终占据着不可或缺的位置&#xff0c;是人们获取营养的重要来源。如今&#xff0c;市场上鸡蛋种类丰富&#xff0c;除了常见的普通鸡蛋&#xff0c;有机黑鸡蛋也逐渐崭露头角&#xff0c;其价格通常略高于普通鸡蛋。这两者究竟存在哪些…

CTFHub-RCE 命令注入-无过滤

观察源代码 判断是Windows还是Linux 源代码中有 ping -c 4 说明是Linux 查看有哪些文件 127.0.0.1|ls 发现除了index.php文件外&#xff0c;还存在一个可疑的文件 打开flag文件 我们尝试打开这个文件 127.0.0.1|cat 19492844826916.php 可是发现 文本内容显示不出来&…

leetcode hot100刷题日记——31.二叉树的直径

二叉树直径详解 题目描述对直径的理解解答&#xff1a;dfs小TIPS 题目描述 对直径的理解 实际上&#xff0c;二叉树的任意一条路径均可以被看作由某个节点为起点&#xff0c;从其左儿子和右儿子向下遍历的路径拼接得到。 那我们找二叉树的直径&#xff08;最大路径&#xff09…

行为型:解释器模式

目录 1、核心思想 2、实现方式 2.1 模式结构 2.2 实现案例 3、优缺点分析 4、适用场景 5、注意事项 1、核心思想 目的&#xff1a;针对某种语言并基于其语法特征创建一系列的表达式类&#xff08;包括终极表达式与非终极表达式&#xff09;​&#xff0c;利用树结构模式…

linux 1.0.3

挂载 这个虚拟机啥时候都能挂起 会有一个这个东东 选择连接虚拟机&#xff0c;然后就连到linux了 这有两个键&#xff0c;一个是和主机连接一个是和虚拟机连接 先把U盘拔掉 原本是没有这个盘的&#xff0c;但是插上去之后&#xff0c;电脑创建了一个虚拟的盘 也就是图中的F…

C#集合循环删除某些行

你想要在遍历集合&#xff08;例如List&#xff09;的同时删除某些元素时&#xff0c;直接在循环中删除元素可能会导致问题&#xff0c;因为这可能会改变集合的大小和导致索引问题&#xff1b; 可以用for循环的倒序来删除&#xff1b; 如果要删除满足特定条件的所有元素&…

【Linux 学习计划】-- 进程地址空间

目录 进程地址的引入 进程地址空间基础原理 区域划分的本质 如何理解进程地址空间 越界访问的本质 进一步理解写时拷贝 重谈 fork 返回值 结语 进程地址的引入 我们先来看一段代码&#xff1a; 首先我们可以看到&#xff0c;父进程和子进程是可以同时可以看到一个变量…

CTFHub-RCE 命令注入-过滤空格

观察源代码 代码里面可以发现过滤了空格 判断是Windows还是Linux 源代码中有 ping -c 4 说明是Linux 查看有哪些文件 127.0.0.1|ls 打开flag文件 我们尝试将空格转义打开这个文件 利用 ${IFS} 127.0.0.1|cat${IFS}flag_195671031713417.php 可是发现 文本内容显示不出来&…

Express教程【002】:Express监听GET和POST请求

文章目录 2、监听post和get请求2.1 监听GET请求2.2 监听POST请求 2、监听post和get请求 创建02-app.js文件。 2.1 监听GET请求 1️⃣通过app.get()方法&#xff0c;可以监听客户端的GET请求&#xff0c;具体的语法格式如下&#xff1a; // 1、导入express const express req…

【PostgreSQL 03】PostGIS空间数据深度实战:从地图服务到智慧城市

PostGIS空间数据深度实战&#xff1a;从地图服务到智慧城市 关键词 PostGIS, 空间数据库, 地理信息系统, GIS, 空间查询, 地理分析, 位置服务, 智慧城市, 空间索引, 坐标系统 摘要 PostGIS是PostgreSQL的空间数据扩展&#xff0c;它将普通的关系数据库转变为强大的地理信息系统…

HIT-csapp大作业:程序人生-HELLO‘s P2P

计算机系统 大作业 题 目 程序人生-Hello’s P2P 专 业 计算学部 学  号 2023111813 班 级 23L0518 学 生 鲁永哲 指 导 教 师 史先俊 计…

深入探讨redis:主从复制

前言 如果某个服务器程序&#xff0c;只部署在一个物理服务器上就可能会面临一下问题(单点问题) 可用性问题&#xff0c;如果这个机器挂了&#xff0c;那么对应的客户端服务也相继断开性能/支持的并发量有限 所以为了解决这些问题&#xff0c;就要引入分布式系统&#xff0c…

帕金森常见情况解读

一、身体出现的异常节奏​ 帕金森会让身体原本协调的 “舞步” 出现错乱。它是一种影响身体行动能力的状况&#xff0c;随着时间推进&#xff0c;就像老旧的时钟&#xff0c;齿轮转动不再顺畅&#xff0c;使得身体各个部位的配合逐渐失衡&#xff0c;打乱日常行动的节奏。​ …