数据结构与算法:图论——深度优先搜索dfs

深度优先搜索dfs

提到深度优先搜索(dfs),就不得不说和广度优先搜索(bfs)有什么区别

根据搜索方式的不同,可以将图的遍历分为「深度优先搜索」和「广度优先搜索」。

  • 深度优先搜索:从某一顶点出发,沿着⼀条路径⼀直搜索下去,在⽆法搜索时,回退到刚刚访问过的节点。
  • 广度优先搜索:从某个顶点出发,⼀次性访问所有未被访问的邻接点,再依次从这些已访问过的邻接点出发,⼀层⼀层地访问。

基础与模板:

不断深入,更新标记

下图来自:AlgoNote/docs/06_graph/06_03_graph_dfs.md at main · itcharge/AlgoNote · GitHub

image-20250624101309420

两种两种深度优先搜索的实现方式

  • 递归实现
  • 基于堆栈实现

首先介绍递归实现。与算法本身非常的和谐,和二叉树的遍历的两种方式很像,实现方法也类似,只不过图的储存方式跟二叉树不同而已。

image-20250624110932548

基于堆栈实现:深度优先搜索是使用stack比较合适

image-20250624114822458

题目1、797. 所有可能的路径

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

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

示例 1:

img

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

示例 2:

img

输入: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]]

提示:

  • n == graph.length
  • 2 <= n <= 15
  • 0 <= graph[i][j] < n
  • graph[i][j] != i(即不存在自环)
  • graph[i] 中的所有元素 互不相同
  • 保证输入为 有向无环图(DAG)

这个题目使用深度优先搜索,用stack实现感觉代码量比较多一些。这里使用递归实现比较好理解,而且代码量比较少,需要考虑的细节也比较多。需要改变DFS的递归条件

找出从节点 0 到节点 n-1 的所有路径并输出,所以递归的时候碰见n-1就把这一组加入结果

image-20250624164453567

class Solution {
public:vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {vector<vector<int>> res;vector<int> singleVec{0};   // 这里开头0先加到中间vec里面int num = graph.size();function<void(vector<int>&)> dfs = [&](vector<int>& vec){if(singleVec.back() == num-1){	// 中间vec最后一个值判断条件res.push_back(singleVec);return;}for(auto a:vec){singleVec.push_back(a);dfs(graph[a]);singleVec.pop_back();// 这里singleVec在外面有push就需要pop}};dfs(graph[0]);return res;}
};

题目2、200. 岛屿数量

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]
]
输出:1

示例 2:

输入:grid = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]
]
输出:3

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] 的值为 '0''1'

经典题目:往上下左右四个方向DFS,先遍历,碰到1就开开始DFS把周围的1都变成0

image-20250624174938394

	int dir[4][2]={0,1,1,0,0,-1,-1,0}; // 上下左右void makezero(vector<vector<char>>& grid,int x,int y){grid[x][y] = '0';    	// 碰见1以后上下左右都置零for(int i = 0;i<4;i++){int nx = x+dir[i][0];int ny = y+dir[i][1];if(nx<0||ny<0||nx>=grid.size()||ny>=grid[0].size()) continue;if(grid[nx][ny]=='1'){makezero(grid,nx,ny);}}}int numIslands(vector<vector<char>>& grid) {int row = grid.size();int col = grid[0].size();int res = 0;for(int i = 0 ;i<row;i++){for(int j =0;j<col;j++ ){if(grid[i][j] == '1' ){ //碰见1之后,周围置零makezero(grid,i,j);res++;}}}return res;}

题目3、695. 岛屿的最大面积

给你一个大小为 m x n 的二进制矩阵 grid

岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

岛屿的面积是岛上值为 1 的单元格的数目。

计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0

示例 1:

img
输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
输出:6
解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。

示例 2:

输入:grid = [[0,0,0,0,0,0,0,0]]
输出:0

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • grid[i][j]01

这个题目只是在外面加一个参数count,遍历数组,得到count的最大值

image-20250624181738358

题目4、133. 克隆图

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。

图中的每个节点都包含它的值 valint) 和其邻居的列表(list[Node])。

class Node {public int val;public List<Node> neighbors;
}

测试用例格式:

简单起见,每个节点的值都和它的索引相同。例如,第一个节点值为 1(val = 1),第二个节点值为 2(val = 2),以此类推。该图在测试用例中使用邻接列表表示。

邻接列表 是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。

给定节点将始终是图中的第一个节点(值为 1)。你必须将 给定节点的拷贝 作为对克隆图的引用返回。

image-20250624201046963
class Solution {
public:vector<int> flags  = vector<int>(101,1);unordered_map<Node*,Node*>mp;
}
这里的flags  为什么不能 vector<int> flags(101,1);

在 C++ 类的定义里,成员变量的初始化要遵循特定的语法规则。当你在类中声明非静态成员变量时,像下面这样直接初始化是不被允许的:

vector<int> flags(101,1); // 错误的类内初始化方式

这是因为这种圆括号初始化的形式,会被编译器理解成函数声明。也就是说,编译器会把它误认为是一个名为flags,返回类型为vector,并且带有两个参数(一个int类型和一个int类型)的函数。

而使用等号加花括号或者直接用花括号的初始化方式,就不会产生歧义,所以是合法的类内初始化形式:

vector<int> flags = vector<int>(101,1); // 正确,使用等号初始化
vector<int> flags{101,1}; // 正确,使用花括号列表初始化(但这种方式是创建包含两个元素101和1的vector)
vector<int> flags = {101,1}; // 正确,等价于上面的花括号形式

在你的代码中:

vector<int> flags  = vector<int>(101,1);

这是正确的类内初始化写法。它借助拷贝初始化,先构造出一个临时的vector对象,然后再把这个临时对象拷贝给成员变量flags。

总结一下,类内初始化要避开圆括号语法,防止和函数声明混淆。建议优先采用花括号初始化或者等号初始化的方式。

这个题目,主要是

  • 首先,dfs时要确定好终止条件,当新节点创立好了之后就不用创立了,返回这个新节点即可
  • 怎么找到创立好的新节点呢,通过哈希表建立起新旧节点的关系,通过旧节点找到新节点
class Node {
public:int val;vector<Node*> neighbors;Node() {val = 0;neighbors = vector<Node*>();}Node(int _val) {val = _val;neighbors = vector<Node*>();}Node(int _val, vector<Node*> _neighbors) {val = _val;neighbors = _neighbors;}
};
class Solution {
public:// 这里为了判断这个节点有没有被创建vector<int> flags  = vector<int>(101,1); // 这个建立原始图节点和新建节点之间映射// 当neighbors中有已经创建的节点之后// 通过这个哈希表就可以找到新建的对应节点unordered_map<Node*,Node*>mp;Node* cloneGraph(Node* node) {if(!node) return nullptr;if(flags[node->val] == 0) return mp[node];// 节点已创建,返回新节点Node* res = new Node(node->val);// 新建节点mp[node] = res;		  // 建立新旧节点哈希表flags[node->val] = 0; // 表示新节点已经创建for(auto a:node->neighbors){res->neighbors.push_back(cloneGraph(a));}					// 新节点neighborsreturn res;}
};

上面是个人写的代码,让deepseek优化之后发现,这个创建标识符flags可以省略,通过哈希表就可以完成这个是否创建的判断

class Solution {
public:unordered_map<Node*, Node*> visited; // 存储原节点到克隆节点的映射Node* cloneGraph(Node* node) {if (!node) return nullptr;if (visited.find(node) != visited.end()) return visited[node]; // 已克隆则直接返回Node* cloneNode = new Node(node->val); // 创建新节点visited[node] = cloneNode; // 建立映射关系for (Node* neighbor : node->neighbors) {cloneNode->neighbors.push_back(cloneGraph(neighbor)); // 递归克隆邻居}return cloneNode;}
};

题目5、841. 钥匙和房间

n 个房间,房间按从 0n - 1 编号。最初,除 0 号房间外的其余所有房间都被锁住。你的目标是进入所有的房间。然而,你不能在没有获得钥匙的时候进入锁住的房间。

当你进入一个房间,你可能会在里面找到一套 不同的钥匙,每把钥匙上都有对应的房间号,即表示钥匙可以打开的房间。你可以拿上所有钥匙去解锁其他房间。

给你一个数组 rooms 其中 rooms[i] 是你进入 i 号房间可以获得的钥匙集合。如果能进入 所有 房间返回 true,否则返回 false

image-20250624204447096


这里就是dfs的模板题。把图从开头遍历,设置flags,表示遍历到这个节点,,否则返回false

image-20250624213220582

class Solution {
public:bool canVisitAllRooms(vector<vector<int>>& rooms) {vector<int> flags(rooms.size(),1); // 标准位,1表示未遍历到flags[0] = 0;					   // 0已经遍历到,0有钥匙function<void(vector<int>&)>dfs = [&](vector<int>& vec){for(auto a:vec){if(flags[a]){		flags[a] = 0;		// 遍历到置零dfs(rooms[a]);		// 继续遍历到下一个vec}}};dfs(rooms[0]);for(auto b:flags){		// 如果这个flags全部被遍历到之后,返回tureif(b == 1) return false;}return true;}
};

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

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

相关文章

数组题解——​合并区间【LeetCode】

56. 合并区间 排序&#xff1a; 将所有区间按起始位置 start 从小到大排序。这样&#xff0c;重叠的区间会相邻排列&#xff0c;方便后续合并。 合并&#xff1a; 初始化一个空列表 merged&#xff0c;用于存储合并后的区间。遍历排序后的区间列表&#xff1a; 如果 merged 为…

关于高精度和链表的详细讲解(从属于GESP五级)

本章内容 高精度 链表 位数再多&#xff0c;只管稳稳进位&#xff0c;终会把答案写满。 一、高精度 1. 什么是高精度 • 定义 “高精度整数”指不受 C 原生整型 (int / long long) 位宽限制&#xff0c;而用数组模拟任意位数的大整数。 • 必要性 64 位 long long 仅能…

Python自动化框架选型指南:Selenium/Airflow/Celery该选谁?

在Python自动化领域,Selenium、Airflow和Celery是三个高频出现的工具,但它们的定位和适用场景截然不同。许多开发者在技术选型时容易混淆它们的边界,导致项目架构臃肿或功能不匹配。本文将通过对比分析,帮你明确不同场景下的最佳选择。 一、框架定位与核心功能对比 框架核…

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | DrinkWater(喝水记录组件)

&#x1f4c5; 我们继续 50 个小项目挑战&#xff01;—— DrinkWater组件 仓库地址&#xff1a;https://github.com/SunACong/50-vue-projects 项目预览地址&#xff1a;https://50-vue-projects.vercel.app/ 使用 Vue 3 的 Composition API 和 <script setup> 语法结…

UAVAI-YOLO:无人机航拍图像的小目标检测模型

摘要 针对无人机航拍图像目标检测效果差的问题&#xff0c;提出改进的UAVAI-YOLO模型。首先&#xff0c;为使模型获得更加丰富的语义信息&#xff0c;使用改进可变形卷积网络&#xff08;deformable convolutional networks&#xff0c;DCN&#xff09;替换原骨干&#xff08…

Solidity 入门教程(一):Hello Web3,从一个字符串开始!

学习 Solidity 最好的方式&#xff0c;就是写出你的第一个合约&#xff01;在本篇文章中&#xff0c;我们将用极简的代码&#xff0c;通过 Remix 平台快速实现并运行一个 “Hello Web3!” 合约&#xff0c;正式迈入智能合约开发的大门。 一、什么是 Solidity&#xff1f; Sol…

串扰与包地

串扰与包地&#xff1a; 串扰与包地一直是业界非常关心的一个问题&#xff0c;围绕着它们的争论非常多&#xff0c;那到底是包地好 还是不包地好呢?高速先生尝试着从理论和实际测试上来给大家做一个分析。 为了验证它&#xff0c;高速先生做了以下几种情况&#xff0c;如图5-…

leetcode hot 100之:二叉树的最近公共祖先

本来不打算写的哈哈哈但是发现这一道递归我是有思路的&#xff01;&#xff01;自己能想到一些方向&#xff01;我真棒&#xff01;所以记录一下哈哈哈 我的思路&#xff1a; 1、祖先一定是自身或往上找&#xff0c;所以如何逆着走呢&#xff1f; 2、3种情况&#xff1a; 有…

【VUE】某时间某空间占用情况效果展示,vue2+element ui实现。场景:会议室占用、教室占用等。

某时间某空间占用情况效果展示&#xff0c;vue2element ui实现。场景&#xff1a;会议室占用、教室占用等。 场景说明&#xff1a; 现在需要基于vue2和el-table实现每日会议室个时间点占用情况。 已知数据&#xff1a; 1、会议室数据&#xff08;名称&#xff0c;id&#xff…

Git更换源方式记录

本文首发地址&#xff1a;https://www.dawnsite.cn/archives/198.html 该方式前提是本地项目已关联远程仓库&#xff0c;由于业务变更git地址改变 1. 移除本地已有远程仓库 git remote remove origin2. 添加新的远程仓库源 git remote add origin "clone地址"3.一步…

前端面试专栏-主流框架:12. Vue3响应式原理与API

&#x1f525; 欢迎来到前端面试通关指南专栏&#xff01;从js精讲到框架到实战&#xff0c;渐进系统化学习&#xff0c;坚持解锁新技能&#xff0c;祝你轻松拿下心仪offer。 前端面试通关指南专栏主页 前端面试专栏规划详情 Vue3响应式原理与API详解 一、引言 Vue3作为Vue.j…

DAY 37 早停策略和模型权重的保存

早停策略 import torch.nn as nn import torch.optim as optim import time import matplotlib.pyplot as plt from tqdm import tqdm# Define the MLP model class MLP(nn.Module):def __init__(self):super(MLP, self).__init__()self.fc1 nn.Linear(X_train.shape[1], 10)s…

零基础搭建Spring AI本地开发环境指南

Spring AI 是一个 Spring 官方团队主导的开源项目&#xff0c;旨在将生成式人工智能&#xff08;Generative AI&#xff09;能力无缝集成到 Spring 应用程序中。它提供了一个统一的、Spring 风格的抽象层&#xff0c;简化了与各种大型语言模型&#xff08;LLMs&#xff09;、嵌…

windows登录系统配置双因子认证的解决方案

在数字化浪潮席卷全球的今天&#xff0c;安全如同氧气般不可或缺。Verizon《2023年数据泄露调查报告》指出&#xff0c;80%的黑客攻击与登录凭证失窃直接相关。当传统密码防护变得千疮百孔&#xff0c;企业如何在身份验证的战场上赢得主动权&#xff1f;答案就藏在"双保险…

Java数据结构——线性表Ⅱ

一、链式存储结构概述 1. 基本概念&#xff08;逻辑分析&#xff09; 核心思想&#xff1a;用指针将离散的存储单元串联成逻辑上连续的线性表 设计动机&#xff1a;解决顺序表 "预先分配空间" 与 "动态扩展" 的矛盾 关键特性&#xff1a; 结点空间动态…

技术基石:SpreadJS 引擎赋能极致体验

在能源行业数字化转型的浪潮中&#xff0c;青岛国瑞信息技术有限公司始终以技术创新为核心驱动力&#xff0c;不断探索前沿技术在能源领域的深度应用。其推出的 RCV 行列视生产数据应用系统之所以能够在行业内脱颖而出&#xff0c;离不开背后强大的技术基石 ——SpreadJS 引擎。…

Typora - Typora 打字机模式

Typora 打字机模式 1、基本介绍 Typora 打字机模式&#xff08;Typewriter Mode&#xff09;是一种专注于当前写作行的功能 打字机模式会自动将正在编辑的行保持在屏幕中央&#xff0c;让用户更集中注意力&#xff0c;类似于传统打字机的体验 2、开启方式 点击 【视图】 -…

3.0 compose学习:MVVM框架+Hilt注解调用登录接口

文章目录 前言&#xff1a;1、添加依赖1.1 在settings.gradle.kts中添加1.2 在应用级的build.gradle.kts添加插件依赖1.3 在module级的build.gradle.kts添加依赖 2、实体类2.1 request2.2 reponse 3、网络请求3.1 ApiService3.2 NetworkModule3.3 拦截器 添加token3.4 Hilt 的 …

git学习资源

动画演示&#xff1a;Learn Git Branching 终极目标&#xff08;能看懂即入门&#xff09;&#xff1a;git 简明指南 Git 教程 | 菜鸟教程

C++ 第二阶段:模板编程 - 第一节:函数模板与类模板

目录 一、模板编程的核心概念 1.1 什么是模板编程&#xff1f; 二、函数模板详解 2.1 函数模板的定义与使用 2.1.1 基本语法 2.1.2 示例&#xff1a;通用交换函数 2.1.3 类型推导规则 2.2 函数模板的注意事项 2.2.1 普通函数与函数模板的调用规则 2.2.2 隐式类型转换…