Day62|图part1:深度优先搜索理论基础、797. 所有可能的路径

news2024/12/24 16:13:03

深度优先搜索(DFS)理论基础

图的实质和存储方式

图实际上就是一棵多叉树,可以用以下的数据结构进行表示:

class Vertex {
    int id;
    vector<Vertex*> neighbors;
};

多叉树的:

/* 基本的 N 叉树节点 */
class TreeNode {
public:
    int val;
    vector<TreeNode*> children;
};

但实际上很少用这个实现图,一般用邻接表或邻接矩阵

image

image

邻接表很直观,我把每个节点 x 的邻居都存到一个列表里,然后把 x 和这个列表关联起来,这样就可以通过一个节点 x 找到它的所有相邻节点。

邻接矩阵则是一个二维布尔数组,我们权且称为 matrix,如果节点 xy 是相连的,那么就把 matrix[x][y] 设为 true(上图中绿色的方格代表 true)。如果想找节点 x 的邻居,去扫一圈 matrix[x][..] 就行了。

如果用代码的形式来表现,邻接表和邻接矩阵大概长这样:

// 邻接表
// graph[x] 存储 x 的所有邻居节点
vector<int> graph[];

// 邻接矩阵
// matrix[x][y] 记录 x 是否有一条指向 y 的边
bool matrix[][];

对于邻接表,好处是占用的空间少。

你看邻接矩阵里面空着那么多位置,肯定需要更多的存储空间。

但是,邻接表无法快速判断两个节点是否相邻。

比如说我想判断节点 1 是否和节点 3 相邻,我要去邻接表里 1 对应的邻居列表里查找 3 是否存在。但对于邻接矩阵就简单了,只要看看 matrix[1][3] 就知道了,效率高。

图的遍历

图的遍历就是多叉树的遍历,经历回溯的过程,首先看下多叉树的遍历框架:

/* 多叉树遍历框架 */
void traverse(TreeNode* root) {
    if (root == nullptr) return;
    // 前序位置
    for (TreeNode* child : root->children) {
        traverse(child);
    }
    // 后序位置
}

图和多叉树最大的区别是,图是可能包含环的,你从图的某一个节点开始遍历,有可能走了一圈又回到这个节点,而树不会出现这种情况,从某个节点出发必然走到叶子节点,绝不可能回到它自身。

所以,如果图包含环,遍历框架就要一个 visited 数组进行辅助:

// 记录被遍历过的节点
vector<bool> visited;
// 记录从起点到当前节点的路径
vector<bool> onPath;

/* 图遍历框架 */
void traverse(Graph graph, int s) {
    if (visited[s]) return;
    // 经过节点 s,标记为已遍历
    visited[s] = true;
    // 做选择:标记节点 s 在路径上
    onPath[s] = true;
    for (int neighbor : graph.neighbors(s)) {
        traverse(graph, neighbor);
    }
    // 撤销选择:节点 s 离开路径
    onPath[s] = false;
}

visited和onPath的区别:类比贪吃蛇游戏,visited 记录蛇经过过的格子,而 onPath 仅仅记录蛇身。在图的遍历过程中,onPath 用于判断是否成环,类比当贪吃蛇自己咬到自己(成环)的场景。

看到这个算法其实很像回溯算法的过程,不同的是回溯算法关注的不是节点,而是树枝。他们的区别可以反映在代码上:

// DFS 算法,关注点在节点
void traverse(TreeNode* root) {
    if (root == nullptr) return;
    printf("进入节点 %s", root);
    for (TreeNode* child : root->children) {
        traverse(child);
    }
    printf("离开节点 %s", root);
}

// 回溯算法,关注点在树枝
void backtrack(TreeNode *root) {
    if (root == nullptr) return;
    for (TreeNode* child : root->children) {
        // 做选择
        printf("从 %s 到 %s", root, child);
        backtrack(child);
        // 撤销选择
        printf("从 %s 到 %s", child, root);
    }
}

执行上面第一个的代码,会发现起始点被漏掉了。

因此对于图的遍历应该使用DFS算法,即把 onPath 的操作放到 for 循环外面,否则会漏掉记录起始点的遍历。

即把 onPath 的操作放到 for 循环外面,否则会漏掉记录起始点的遍历。

visited 数组,其目的很明显了,由于图可能含有环,visited 数组就是防止递归重复遍历同一个节点进入死循环的。

当然,如果题目告诉你图中不含环,可以把 visited 数组都省掉,基本就是多叉树的遍历。

深搜三部曲

与回溯三部曲一样,DFS也有一个框架;

void dfs(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本节点所连接的其他节点) {
        处理节点;
        dfs(图,选择的节点); // 递归
        回溯,撤销处理结果
    }
}

  1. 确认递归函数,参数

一般情况,深搜需要 二维数组数组结构保存所有路径,需要一维数组保存单一路径,这种保存结果的数组,我们可以定义一个全局遍历,避免让我们的函数参数过多。

  1. 确认终止条件

终止添加不仅是结束本层递归,同时也是我们收获结果的时候。

另外,其实很多dfs写法,没有写终止条件,其实终止条件写在了, 下面dfs递归的逻辑里了,也就是不符合条件,直接不会向下递归

  1. 处理目前搜索节点出发的路径

一般这里就是一个for循环的操作,去遍历 目前搜索节点 所能到的所有节点。

797. 所有可能的路径

leetcode链接:力扣题目链接

给你一个有 n 个节点的 有向无环图(DAG),
请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序)

graph[i] 是一个从节点 i 可以访问的所有节点的列表(
即从节点 i 到节点 graph[i][j]存在一条有向边)。

示例1:

输入:graph = [[1,2],[3],[3],[]] 
输出:[[0,1,3],[0,2,3]] 
解释:有两条路径 0 -> 1 -> 30 -> 2 -> 3

image

示例II:
输入:graph = [[4,3,1],[3,2,4],[3],[4],[]] 
输出:[[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]
  

image

这里注意到题目要求的是有向无环图,因此可以不用visited数组记录是否重复了。

这里的输入其实就是邻接表,

graph = [[1,2],[3],[3],[]]表示0节点和1,2相连,1节点和3相连等等。

深搜三部曲

  1. 确定递归函数和参数

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

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

vector<vector<int>> result; // 收集符合条件的路径
vector<int> path; // 0节点到终点的路径
// x:目前遍历的节点
// graph:存当前的图
void dfs (vector<vector<int>>& graph, int x) 
  1. 确定终止条件

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

当前遍历的节点,我们定义为x,最后一点节点,就是 graph.size() - 1(因为题目描述是找出所有从节点 0 到节点 n-1 的路径并输出)。

所以 但 x 等于 graph.size() - 1 的时候就找到一条有效路径。 代码如下:

// 要求从节点 0 到节点 n-1 的路径并输出,所以是 graph.size() - 1
if (x == graph.size() - 1) { // 找到符合条件的一条路径
    result.push_back(path); // 收集有效路径
    return;
}
  1. 处理目前搜索节点出发的路径

遍历与x相连的所有节点,查找其邻接表:

for (int i = 0; i < graph[x].size(); i++) { // 遍历节点n链接的所有节点

进入循环后,加入节点,dfs,回溯:

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

最终代码:

class Solution {
private:
    vector<int> path;
    vector<vector<int>> res;
    void dfs(vector<vector<int>>& graph, int x){
        if(x == graph.size() - 1){
            res.push_back(path);
            return;
        }
        for(int i = 0; i < graph[x].size(); i++){
            path.push_back(graph[x][i]);
            dfs(graph, graph[x][i]);
            path.pop_back();

        }
    }
public:
    vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
        path.push_back(0);
        dfs(graph,0);
        return res;
    }
};

总结

  • DFS与回溯算法相似, 也有深搜三部曲;
  • 对于有环图,使用visited数组标注是否遍历过;
  • 注意图的存储方式(邻接表常用)和遍历方式

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

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

相关文章

第一章 计算机系统概述 八、虚拟机

目录 一、传统虚拟机的结构 二、两类虚拟机管理程序 &#xff08;1&#xff09;定义&#xff1a; &#xff08;2&#xff09;区别&#xff1a;&#xff08;考点&#xff09; 一、传统虚拟机的结构 二、两类虚拟机管理程序 &#xff08;1&#xff09;定义&#xff1a; &…

电子企业MES管理系统实施的功能和流程有哪些

MES生产管理系统是一种应用于电子企业的管理系统&#xff0c;旨在提高生产效率、降低浪费、优化资源利用&#xff0c;并实时监控和改善生产过程。在电子企业中&#xff0c;实施MES管理系统对于实现精细化管理、增强信息互联、提高产品质量和交货期等方面具有重要作用。 一、MES…

模电课设:用Multisim了解三极管特性及计算静态工作点

1 课设内容 1&#xff09;绘制三极管输入特性曲线、输出特性曲线、放大倍数的幅频特性&#xff1b; 2&#xff09;测试三极管放大倍数β与VBE的关系、放大倍数与温度的关系&#xff1b; 3&#xff09;利用Multisim计算静态工作点。 2 模型搭建 电路一&#xff1a;绘制三极管…

字节一面:如何实现单行/多行文本溢出的省略样式?

前言 最近博主在字节面试中遇到这样一个面试题&#xff0c;这个问题也是前端面试的高频问题&#xff0c;作为一名前端开发工程师&#xff0c;css是我们的必备技能&#xff0c;文本溢出问题也是我们经常遇到的问题&#xff0c;我们不仅只是处理这种情况&#xff0c;还要考虑他的…

微信小程序 通过 pageScrollTo 滚动到界面指定位置

我们可以先创建一个page 注意 一定要在page中使用 因为pageScrollTo控制的是页面滚动 你在组件里用 他就失效了 我们先来看一个案例 wxml 代码如下 <view><button bindtap"handleTap">回到指定位置</button><view class "ControlHeight…

证件照电子版怎么做?学会这个方法自己就会做证件照

自己制作证件照的好处在于可以根据需要调整照片的大小、背景颜色以及美颜等设置&#xff0c;以满足不同场景下的使用需求。此外&#xff0c;自己制作证件照还可以更好地保护个人隐私&#xff0c;避免使用公共设备拍摄证件照时泄露个人信息。 总的来说&#xff0c;自己制作证件…

Webpack Sourcemap文件泄露漏洞

Webpack Sourcemap文件泄露漏洞 前言一、Webpack和Sourcemap1.1 什么是Webpack1.2 什么是Sourcemap 二、漏洞利用2.1 使用reverse-sourcemap工具2.1 直接看前端代码 三、漏洞挖掘漏洞修复 前言 Webpack主要是用于前端框架进行打包的工具&#xff0c;打包后形成.js.map文件&…

Revit SDK 介绍:CurtainSystem 幕墙系统

前言 这个例子介绍如何创建和修改幕墙系统。 内容 如图所示&#xff0c;创建一个幕墙系统&#xff0c;这个系统里有三个面。 创建幕墙系统 // 遍历体量元素的几何&#xff0c;得到所有的面&#xff0c;保存在 m_mydocument.MassFaceArray // 获取用户选中的面 FaceArray …

哪种IP更适合你的数据抓取需求?

程序员大佬们好&#xff01;今天我要和大家分享一个关于数据抓取的话题&#xff0c;那就是Socks5爬虫ip和动态IP之间的比较。在进行数据抓取时&#xff0c;选择适合自己需求的工具和技术是非常重要的。Socks5爬虫ip和动态IP都是常见的网络工具&#xff0c;它们在数据抓取方面都…

节能灯与led灯哪个对眼睛好一点?精选好用的led护眼灯

节能灯和led灯相比&#xff0c;肯定是led灯更加护眼一点的。 首先节能灯最大的优点就是节能&#xff0c;可以使用的寿命长。可是和led灯相比&#xff0c;led灯的转换效率会更好一点&#xff0c;也更加节能&#xff0c;可使用寿命也很长。其次节能灯他启动会比较慢&#xff0c;散…

DC电源模块在节省能源方面上的优秀表现

BOSHIDA DC电源模块在节省能源方面上的优秀表现 DC电源模块是现代电子产品中广泛应用的一种电源转换设备&#xff0c;其能够将交流电转换成直流电&#xff0c;并为电子设备提供稳定、可靠的电源供应。相比于传统的交流电源&#xff0c;DC电源模块在节省能源方面上具有优秀的表…

【开发】React框架下如何集成H.265网页流媒体EasyPlayer.js视频播放器?

H5无插件流媒体播放器EasyPlayer属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;可支持H.264与H.265编码格式&#xff0c;性能稳定、播放流畅&#xff0c;能支持WebSocket-FLV、HTTP-FLV&#xff0c;HLS&#xff08;m3u8&#…

人形机器人闭门研讨会报名丨青源Workshop第24期

青源Workshop丨No.24 人形机器人主题闭门研讨会 在全球普遍面临人口老龄化问题的情况下&#xff0c;人形机器人作为一种新兴的技术领域&#xff0c;正逐步在工业、服务、医疗等领域崭露头角&#xff0c;为制造业、农业以及各种服务业带来了新的机遇与解决方案。与传统的工业机器…

激活函数总结(三十二):激活函数补充(Serf、FReLU)

激活函数总结&#xff08;三十二&#xff09;&#xff1a;激活函数补充 1 引言2 激活函数2.1 Serf激活函数2.2 FReLU激活函数 3. 总结 1 引言 在前面的文章中已经介绍了介绍了一系列激活函数 (Sigmoid、Tanh、ReLU、Leaky ReLU、PReLU、Swish、ELU、SELU、GELU、Softmax、Soft…

iPhone 15 Pro深度评测:猫头虎技术团队揭秘Apple最新航空级钛设计的魅力与创新

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f405;&#x1f43e;猫头虎建议程序员必备技术栈一览表&#x1f4d6;&#xff1a; &#x1f6e0;️ 全栈技术 Full Stack: &#x1f4da…

python-爬虫-requests

安装模块 pip install requests在jupyter notebook里使用ShiftTab查看 requests requests库的主要方法 方法解释requests.requset()构造一个请求&#xff0c;支持以下各种方法requests.get()获取HTML的主要方法requests.head()获取HTML头部信息requests.post()向HTML网页提…

【数据结构】堆排序与TopK问题

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.堆的概念和结构 2.堆的实现 2.1向上调整…

(一)探索随机变量及其分布:概率世界的魔法

文章目录 &#x1f34b;引言&#x1f34b;什么是随机变量&#xff1f;&#x1f34b;离散随机变量&#x1f34b;连续随机变量 &#x1f34b;随机变量的概率分布&#x1f34b;离散概率分布&#x1f34b;0-1分布&#xff08;Bernoulli分布&#xff09;&#x1f34b;二项分布&#…

react频繁使用的js(input防抖请求、节流)

目录 react频繁使用的js(input防抖请求)input的防抖请求节流 提交的节流或者点击按钮等节流节流 code节流 效果 react频繁使用的js(input防抖请求) input的防抖请求 import React, { useState, useEffect, useCallback } from "react"; import { Input } from &quo…

【数据结构】【项目】BitMap?40亿电话号码如何快速去重?

目录 前言实现完整代码 参考资料 前言 40亿电话号码如何快速去重&#xff1f;我们往往会想到bitmap 数据结构中的 Bitmap 是一种位图索引非常高效的数据结构&#xff0c;用于存储处理大规模数据的位信息&#xff0c;其中每个位对应于一个元素&#xff0c;如果位为1&#xff0…