代码随想录算法训练营第四十三天 | 图论理论基础、深搜理论基础、98. 所有可达路径、广搜理论基础

图论理论基础

文章讲解:图论理论基础 | 代码随想录

视频讲解:如果学不会图论,是因为你没掌握基础!图论算法基础篇来咯!_哔哩哔哩_bilibili
大家可以在看图论理论基础的时候,很多内容 看不懂,例如也不知道 看完之后 还是不知道 邻接矩阵,邻接表怎么用, 别着急。

理论基础大家先对各个概念有个印象就好,后面在刷题的过程中,每个知识点都会得到巩固。


深搜理论基础

文章讲解:深度优先搜索理论基础 | 代码随想录

视频讲解:妈妈再也不在担心我的图论!!深度优先搜索理论基础来咯! 深搜理论基础篇_哔哩哔哩_bilibili
了解一下深搜的原理和过程


98. 所有可达路径

题目链接:98. 可达路径

文章讲解:98. 所有可达路径 | 代码随想录

视频讲解:图论,深度优先搜索基础题,深搜详解 | 卡码网:98. 所有可达路径_哔哩哔哩_bilibili
重点在图的存储,深搜流程以及打印图三个部分,要弄清楚这三个部分是如何写的。

一、图的存储

图论理论基础篇 中我们讲到了 两种 图的存储方式:邻接表 和 邻接矩阵。

本题我们将带大家分别实现这两个图的存储方式。

1.邻接矩阵

邻接矩阵 使用 二维数组来表示图结构。 邻接矩阵是从节点的角度来表示图,有多少节点就申请多大的二维数组。

本题我们会有n 个节点,因为节点标号是从1开始的,为了节点标号和下标对齐,我们申请 n + 1 * n + 1 这么大的二维数组。

cpp 复制代码
vector<vector<int>> graph(n + 1, vector<int>(n + 1, 0));

输入m个边,构造方式如下:

cpp 复制代码
while (m--) {
    cin >> s >> t;
    // 使用邻接矩阵 ,1 表示 节点s 指向 节点t
    graph[s][t] = 1;
}
2.邻接表

邻接表 使用 数组 + 链表的方式来表示。 邻接表是从边的数量来表示图,有多少边 才会申请对应大小的链表。

邻接表的构造相对邻接矩阵难理解一些。

我在 图论理论基础篇 举了一个例子:

这里表达的图是:

  • 节点1 指向 节点3 和 节点5
  • 节点2 指向 节点4、节点3、节点5
  • 节点3 指向 节点4
  • 节点4指向节点1

我们需要构造一个数组,数组里的元素是一个链表。

C++写法:

cpp 复制代码
// 节点编号从1到n,所以申请 n+1 这么大的数组
vector<list<int>> graph(n + 1); // 邻接表,list为C++里的链表

输入m个边,构造方式如下:

cpp 复制代码
while (m--) {
    cin >> s >> t;
    // 使用邻接表 ,表示 s -> t 是相连的
    graph[s].push_back(t);
}

本题我们使用邻接表 或者 邻接矩阵都可以,因为后台数据并没有对图的大小以及稠密度做很大的区分。

以下我们使用邻接矩阵的方式来讲解,文末我也会给出 使用邻接表的整体代码。

注意邻接表 和 邻接矩阵的写法都要掌握

二、深度优先搜索

本题是深度优先搜索的基础题目,关于深搜我在图论深搜理论基础 已经有详细的讲解,图文并茂。

关于本题我会直接使用深搜三部曲来分析,如果对深搜不够了解,建议先看 图论深搜理论基础

深搜三部曲来分析题目:

1.确认递归函数,参数

首先我们dfs函数一定要存一个图,用来遍历的,需要存一个目前我们遍历的节点,定义为x。

还需要存一个n,表示终点,我们遍历的时候,用来判断当 x==n 时候 标明找到了终点。

(其实在递归函数的参数 不容易一开始就确定了,一般是在写函数体的时候发现缺什么,参加就补什么)

至于 单一路径 和 路径集合 可以放在全局变量,那么代码是这样的:

cpp 复制代码
vector<vector<int>> result; // 收集符合条件的路径
vector<int> path; // 0节点到终点的路径
// x:目前遍历的节点
// graph:存当前的图
// n:终点
void dfs (const vector<vector<int>>& graph, int x, int n) {
2.确认终止条件

什么时候我们就找到一条路径了?

当目前遍历的节点 为 最后一个节点 n 的时候 就找到了一条 从出发点到终止点的路径。

cpp 复制代码
// 当前遍历的节点x 到达节点n 
if (x == n) { // 找到符合条件的一条路径
    result.push_back(path);
    return;
}
3.处理目前搜索节点出发的路径

接下来是走 当前遍历节点x的下一个节点。

首先是要找到 x节点指向了哪些节点呢? 遍历方式是这样的:

cpp 复制代码
for (int i = 1; i <= n; i++) { // 遍历节点x链接的所有节点
    if (graph[x][i] == 1) { // 找到 x指向的节点,就是节点i
    }
}

接下来就是将 选中的x所指向的节点,加入到 单一路径来。

cpp 复制代码
path.push_back(i); // 遍历到的节点加入到路径中来

进入下一层递归

cpp 复制代码
dfs(graph, i, n); // 进入下一层递归

最后就是回溯的过程,撤销本次添加节点的操作。

为什么要有回溯,我在图论深搜理论基础 也有详细的讲解。

该过程整体代码:

cpp 复制代码
for (int i = 1; i <= n; i++) { // 遍历节点x链接的所有节点
    if (graph[x][i] == 1) { // 找到 x链接的节点
        path.push_back(i); // 遍历到的节点加入到路径中来
        dfs(graph, i, n); // 进入下一层递归
        path.pop_back(); // 回溯,撤销本节点
    }
}

三、打印结果

ACM格式大家在输出结果的时候,要关注看看格式问题,特别是字符串,有的题目说的是每个元素后面都有空格,有的题目说的是 每个元素间有空格,最后一个元素没有空格。

有的题目呢,压根没说,那只能提交去试一试了。

很多录友在提交题目的时候发现结果一样,为什么提交就是不对呢。

例如示例输出是:

1 3 5 而不是 1 3 5

即 5 的后面没有空格!

这是我们在输出的时候需要注意的点。

有录友可能会想,ACM格式就是麻烦,有空格没有空格有什么影响,结果对了不就行了?

ACM模式相对于核心代码模式(力扣) 更考验大家对代码的掌控能力。 例如工程代码里,输入输出都是要自己控制的。这也是为什么大公司笔试,都是ACM模式。

以上代码中,结果都存在了 result数组里(二维数组,每一行是一个结果),最后将其打印出来。(重点看注释)

cpp 复制代码
// 输出结果
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; // 这里再打印倒数第一个,控制最后一个元素后面没有空格
}

四、本题代码

1.邻接矩阵写法
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> result; // 收集符合条件的路径
vector<int> path; // 1节点到终点的路径

void dfs (const vector<vector<int>>& graph, int x, int n) {
    // 当前遍历的节点x 到达节点n 
    if (x == n) { // 找到符合条件的一条路径
        result.push_back(path);
        return;
    }
    for (int i = 1; i <= n; i++) { // 遍历节点x链接的所有节点
        if (graph[x][i] == 1) { // 找到 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<vector<int>> graph(n + 1, vector<int>(n + 1, 0));

    while (m--) {
        cin >> s >> t;
        // 使用邻接矩阵 表示无线图,1 表示 s 与 t 是相连的
        graph[s][t] = 1;
    }

    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;
    }
}
2.邻接表写法
cpp 复制代码
#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;
    }
}

五、总结

本题是一道简单的深搜题目,也可以说是模板题,和 力扣797. 所有可能的路径 (opens new window)思路是一样一样的。

很多录友做力扣的时候,轻松就把代码写出来了, 但做面试笔试的时候,遇到这样的题就写不出来了。

在力扣上刷题不用考虑图的存储方式,也不用考虑输出的格式。

而这些都是 ACM 模式题目的知识点(图的存储方式)和细节(输出的格式)

所以我才会特别制作ACM题目,同样也重点去讲解图的存储和遍历方式,来帮大家去练习。

对于这种有向图路径问题,最合适使用深搜,当然本题也可以使用广搜,但广搜相对来说就麻烦了一些,需要记录一下路径。

而深搜和广搜都适合解决颜色类的问题,例如岛屿系列,其实都是 遍历+标记,所以使用哪种遍历都是可以的。

至于广搜理论基础,我们在下一篇在好好讲解,敬请期待!


LeetCode深搜理论基础

文章讲解:广度优先搜索理论基础 | 代码随想录

视频讲解:​​​​​​​图论:广度优先搜索理论基础! | 广搜理论基础_哔哩哔哩_bilibili

这几章都是基础,大家好好看一下就行,后面有题了都会复习到的,不用着急,现在肯定会有不懂的

相关推荐
罗西的思考1 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX2 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面2 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号2 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽3 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI3 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者4 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者4 小时前
【征程6】校准量化中HistogramObserver解析
算法
OuO-25 小时前
笔试强训 Day 34:ISBN 号码、kotori 和迷宫、矩阵最长递增路径
java·算法·矩阵
程序喵大人6 小时前
【C++进阶】STL算法与函数对象 - 04 find、count和any_of把查询写成意图
开发语言·c++·算法