算法总结篇 —— dfs(搜索、递归、回溯)

有些事情本来很遥远,你争取,它就会离你越来越近

  • 概念 + 名词解释
    • 如何理解递归
  • dfs 回溯类问题
    • 蓝桥杯——飞机降落
  • dfs 迷宫搜索类问题
    • 蓝桥杯——岛屿个数

概念 + 名词解释

递归是指在一个函数的定义中调用自身的过程,有些复杂问题可以划分为多个相同子问题的话,就可以大概率可以使用递归来解决。经典算法如暴搜回溯深度优先遍历(dfs)FloodFile算法记忆化搜索等本质上都是使用递归来解决,换句话来说,这些本质上来说都是一种问题。

如何理解递归

初学者可以在做递归简单问题的时候,画递归展开图来帮助理解递归,如斐波那契数列问题 f(n) = f(n - 1) + f(n - 2),要求出f(4),就得先递归去求解 f(3)和 f(2);

当学习了数据结构之后呢,可以做一些二叉树相关的 OJ 题目去理解递归,但二叉树刚开始做其实都是非常迷的,但慢慢理解后,好像确实就只是这么个事哈哈。

当做了一些简单递归题目和二叉树后,就可以尝试从宏观上理解递归了,可以将递归问题分为三步:

  • 不细想递归展开图,越复杂的问题越不能画递归展开图,递归展开图只适用于在简单题中理解递归
  • 把递归的函数想象成一个黑盒,你让这个黑盒去完成一个任务,并信任这个黑盒一定能完成。只需要设计好这个黑盒即可!
  • 将复杂问题的主逻辑写出来,只在乎递归的结果,并将这个问题解决

那么问题就变为了,如何设计递归函数,也就是如何设计一个这样的黑盒?
首先,找到题目中的多个相同子问题——递归函数头的设计
只关心其中一个子问题是如何解决的——递归函数体的设计
根据经验或题目要求再确定一下递归的出口即可

单看比较抽象,使用一个例子来解释一下上面的步骤:
面试题——汉诺塔

汉诺塔问题,大家都听过吧,将多个圆盘从一个柱A 借助 柱B 移动到 柱C,前提是不小的盘子必须放在大的盘子的上面,问一共有多少种解法?
在这里插入图片描述
当然汉诺塔问题是有公式解决的的,但这没有任何意义,我们的目的是从宏观上理解递归。
当有n个圆盘的时候,我们可以先将(n-1)个圆盘移动到 塔2 上面,然后再将塔1的最后一个大圆盘移至 塔3,再用同样的方法将塔2上的(n-1)个元素借助塔1移至塔3,要将这 n - 1 个移动,就要先移动 n - 2,如此往复…

找到了重复子问题:要移动 n 个,就要先解决 n - 1 个怎么搞,要解决 n - 1 个,就要先解决 n - 2 个怎么搞…,并且函数头需要将这几个柱子全部传过去

在这里插入图片描述

那么其中一个子问题需要怎么解决呢?
假设有 3个柱子ABC吗,n 个盘子,那么需要先将 n - 1 个盘子借助 C 先移动到 B,再将地下那个最大的移动到 C ,然后A 空了,再借助 A 将 B 上的 n - 1 个盘子移动到 C上,就完成了任务。说明在 n 减到 1 之前,都需要重复上面的步骤,先将 n -1 个盘子借助一个柱子移动到另一个上,然后将第 n 个移动。那么函数体的设计:
在这里插入图片描述

在这里插入图片描述
那么,在 n > 1 的时候就要一直递归,直到 n = 1 时才进行第一次返回,所以递归出口就是 n == 1,递归返回前,需要将盘子从 A 移动到 C,然后返回。
在这里插入图片描述

汉诺塔力扣代码:

class Solution {
public:void _haota(vector<int>& A, vector<int>& B, vector<int>& C, int n){if(n == 1){C.push_back(A.back());A.pop_back();return;}_haota(A, C, B, n - 1);C.push_back(A.back());A.pop_back(); _haota(B, A, C, n - 1);}void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {int n = A.size(); // 需要移动的盘子数量_haota(A, B, C, n);}
};

其实我也不知道自己到底说没说明白…

dfs 回溯类问题

步骤:
先使用决策树画出所有情况,如对 [1, 2, 3] 进行全排列
在这里插入图片描述
使用 全局变量path 来记录某条路径上的结果,在决策树画到底部的时候,判断 path 中的结果是否符合结果,然后需要“向上返回”,并且顺便“恢复现场”,如上图中,path 中在底部变成了 123,恢复现场后就成了 12;如果没有可以继续走的路,就继续“向上返回”,再恢复现场,然后判断是否有其他路径可以走,遍历完决策树中所有情况后,就暴搜出了所有情况。
剪枝
即剪去不合理的分支,当全排列的时候,某个数字被列举了之后,就不能再使用了,因此要剪去这个分支!一般我们可以使用 bool 类型的check[] 数组来实现剪枝。

全排列力扣代码

class Solution {
public:vector<vector<int>> ret;vector<int> path;bool check[7] = { false };vector<vector<int>> permute(vector<int>& nums) {dfs(nums);return ret;}void dfs(vector<int>& nums){if(path.size() == nums.size()){ret.push_back(path); // 将合理路径放入最终结果return;}for(int i = 0; i < nums.size(); i++){if(!check[i]){check[i] = true;path.push_back(nums[i]);dfs(nums);check[i] = false; // 恢复现场path.pop_back(); // 恢复现场}}}
};

蓝桥杯——飞机降落

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这道题的数据要求不算高,我们就可以考虑将所有情况都枚举一遍,也就是暴搜!

#include<bits/stdc++.h>
using namespace std; 
// 飞机降落 
struct plane
{int a; // 到达时间 int b; // 可以盘旋的时间 int c; // 降落所需的时间 
};int T; // 组数 
int N; // 每组的飞机数 
plane vp[12];
bool check[12];
bool dfs(int num, int last)//num 为第几个, last 为上一架的降落时间 
{if(num == N){return true;// 能走到这一步,说明符合题意且结束了 }for(int i = 0; i < N; i++){if(!check[i] && vp[i].a + vp[i].b >= last)// 说明可以降落 {check[i] = true;if(dfs(num + 1, max(last, vp[i].a) + vp[i].c)) return true;check[i] = false;}}return false;// 全部都遍历, 如果没有满足情况的话, 说明不行, 就返回 
}
int main()
{cin >> T; // 组数for(int i = 0; i < T; i++){cin >> N;for(int i = 0; i < N; i++){cin >> vp[i].a >> vp[i].b >> vp[i].c;}if(dfs(0, 0)) cout << "YES" << endl;else cout << "NO" << endl;}return 0;
}

dfs 迷宫搜索类问题

搜索类问题一般都是在二维矩阵中进行,以某个坐标为起点进行一次/多次深度优先遍历,直到遇到某个位置停止。
在二维矩阵中搜索,一般可以定义两个向量数组

    // 当题目要求只能上下左右四个方向走时int dx[4] = { 0, 0, 1, -1 };int dy[4] = { 1, -1, 0, 0 };// 如当前的位置坐标为 (i, j), 那么可以走的位置有:(矩阵 m 行 n 列)for(int k = 0; k < 4; k++){int x = dx[k] + i, int y = dy[k] + j;if(x >= 0 && x < m && y >= 0 && y < n && !visit[x][y] && /* 题目要求条件*/){visit[x][y] = true;dfs(x, y, /**/);}}
    // 当题目要求可以上下左右、斜左上、斜右上、斜左下、斜右下这8个方向走时int dx[4] = { 0, 0, 1, -1 , 1, 1, -1, -1};int dy[4] = { 1, -1, 0, 0 , -1, 1, -1, 1};for(int k = 0; k < 8; k++){int x = dx[k] + i, int y = dy[k] + j;if(x >= 0 && x < m && y >= 0 && y < n && !visit[x][y] && /* 题目要求条件*/){visit[x][y] = true;dfs(x, y, /**/);}}

还会定义一个 visit[][] 数组,用来记录某个位置是否被遍历过了

力扣练习题——黄金矿工

参考代码:

class Solution {
public:// 当题目要求只能上下左右四个方向走时int dx[4] = { 0, 0, 1, -1 };int dy[4] = { 1, -1, 0, 0 };// 当题目要求可以上下左右、斜左上、斜右上、斜左下、斜右下这8个方向走时int dx[4] = { 0, 0, 1, -1 , 1, 1, -1, -1};int dy[4] = { 1, -1, 0, 0 , -1, 1, -1, 1};bool visit[16][16] = { false };int ret = 0;int getMaximumGold(vector<vector<int>>& grid) {int m = grid.size(), n = grid[0].size();int gold = 0;for (int i = 0; i < m; i++){for (int j = 0; j < n; j++){if (grid[i][j] != 0){visit[i][j] = true;gold += grid[i][j];dfs(grid, i, j, gold);gold -= grid[i][j];visit[i][j] = false;}}}return ret;}void dfs(vector<vector<int>>& grid, int i, int j, int gold){for (int k = 0; k < 4; k++){int x = i + dy[k], y = j + dx[k];if (x >= 0 && x < grid.size() && y >= 0 && y < grid[0].size() &&grid[x][y] != 0 && !visit[x][y]) {gold += grid[x][y];visit[x][y] = true;dfs(grid, x, y, gold);gold -= grid[x][y];visit[x][y] = false;}}ret = max(ret, gold);return;}
};

蓝桥杯——岛屿个数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

int T;
int M, N;
int nums[12][12];
bool visit[12][12];
int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};
int dxx[8] = {0, 0, 1, -1, 1, 1, -1, -1};
int dyy[8] = {1, -1, 0, 0, -1, 1, 1, -1};
int ret = 0;
void dfs_board(int i, int j)
{for(int k = 0; k < 4; k++){int x = i + dx[k];int y = j + dy[k];if(x >= 1 && x <= M && y >= 1 && y <= N && !visit[x][y] && nums[x][y] == 1){visit[x][y] = true; // 遍历陆地 dfs_board(x, y);}}
}
void dfs_sea(int i, int j)
{for(int k = 0; k < 8; k++){int x = i + dxx[k];int y = j + dyy[k];if(x >= 1 && x <= M && y >= 1 && y <= N && !visit[x][y] && nums[x][y] == 0){visit[x][y] = true; // 将外海全部标出来 dfs_sea(x, y);}if(x >= 1 && x <= M && y >= 1 && y <= N && !visit[x][y] && nums[x][y] == 1)  {visit[x][y] = true;ret ++; // 陆地数量  +  1 dfs_board(x, y); // 当遇到陆地的时候,就去搜索这个陆地的连通块 }}
}
int main()
{cin >> T;while(T--){ret = 0;memset(nums, 0, sizeof(nums));memset(visit, 0, sizeof(visit));cin >> M >> N;for(int i = 1; i <= M; i++) // 将数据存入 nums 二维数组 {string str;cin >> str;for(int j = 1; j <= N; j++){nums[i][j] = str[j - 1] - '0'; // 将数据输入,只有 0 和 1 }}int flag = 0;for(int i = 1; i <= M; i++){for(int j = 1; j <= N; j++){if((i == 1 || i == M || j == 1 || j == N) && nums[i][j] == 0 && !visit[i][j]) // 在边缘位置找外海 {flag = 1;visit[i][j] = true;dfs_sea(i, j); // 从外海往进渗透,有8个方向 }	}}if(flag == 0) ret = 1;cout << ret << endl;}return 0;
}

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

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

相关文章

c++的学习之路:26、AVL树

摘要 本章主要是说一下AVL树的实现&#xff0c;这里说的是插入的底层原理 目录 摘要 一、原理 二、四种旋转 1、左单旋 2、右单旋 3、左右双旋 4、右左双旋 三、代码实现 1、节点创建 2、插入 3、旋转 4、判断是否平衡 5、测试 四、代码 一、原理 前面说了搜索…

为啥转化为可编辑面片后有这么多点和线

可以删一下 按住alt按移除可以删掉 选择你要删的那些线 按住alt点移除

eBay、亚马逊自养号测评如何避免风控账号关联选择合适网络IP环境

在自养号下单中选择适合的网络环境至关重要。经过多次实践与测试&#xff0c;积累了大量的经验&#xff0c;希望能够与大家分享&#xff0c;帮助大家避开陷阱&#xff0c;顺利前行。 市面上的网络环境种类繁多&#xff0c;从纯IP类的Luminati、Rola&#xff0c;到纯环境类的VM…

编写Spark独立应用程序

执行本文之前&#xff0c;先搭建好spark的开发环境&#xff0c;我目前只搭建了standalone模式&#xff0c;参考链接 &#xff1a; Spark Standalone模式部署-CSDN博客 1. 安装sbt 1&#xff09;下载sbt 网址&#xff1a;https://www.scala-sbt.org/download.html &#xff0c…

【云原生 • Docker】 ELK 8.4.3 docker 保姆级安装部署详细步骤

文章目录 ELK简介二、版本说明三、安装部署3.1 创建docker网络3.2 Elasticsearch拉取docker镜像,版本:8.4.3第一次执行docker脚本可以看到控制台的信息,找到这个信息并保存下来创建Elasticsearch挂载目录给创建的文件夹授权将容器内的文件复制到主机上删除容器修改docker脚本…

【GPTs分享】GPTs分享之Image Recreate | img2img​

简介 该GPT是一个专门用于图像编辑、重建和合并的工具。它通过详细的自动图像描述和生成&#xff0c;帮助用户从源图像中重现或修改图像。此工具设计用于为视障用户提供图像内容的详细描述&#xff0c;并生成全新的图像&#xff0c;以满足特定的视觉需求。 主要功能 \1. 图像…

Unity开发holoLens2应用时的ProjectSettings配置

正确的进行Unity工程配置&#xff0c;才能进行后续的【发布】和【部署】操作… 本案例开发环境说明&#xff1a; Unity2021.3.18Win10VS2022HoloLens2 一、平台设置 二、Quality画面质量设置 三、Player玩家设置 四、XR-Plug设置 五、环境测试 导入一个官方demo&#xff0c…

ORAN C平面 Section Extension 23

ORAN C平面Section扩展23用于任意symbol模式的调制压缩参数。此section扩展允许为一个或多个“SymPrbPatterns”指定多组“mcScaleReMask、csf和mcScaleOffset”值。“SymPrbPattern”用于指定一组PRB&#xff0c;这些PRB可以跨越使用prbPattern指定的整个PRB范围&#xff08;频…

重生奇迹mu坐骑怎么升级

重生奇迹mu坐骑怎么升级 1、前期&#xff0c;都是主线任务&#xff0c;我们必须要跟着主线任务走&#xff0c;前面的话升级一次需要的经验很少的&#xff0c;一天下来可以升级100级是轻轻松松的&#xff0c;主线任务是比较多的&#xff0c;我们跟着任务一直做差不多可以到150级…

dspbuilder中使用signalcompiler时报错Error during compilation: Fitter failed,如何解决?

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

计算机组成原理【CO】Ch7 I/O大题

目录 I/O大题解题方法 I/O接口 各种I/O方式的特点 I/O端口编址 程序查询方式 中断控制方式 DMA控制方式 程序中断的工作流程 程序中断的工作流程 DMA方式和中断方式的区别 I/O大题解题方法 CPU 程序查询中断DMA I/O接口的类型 按字传输&#xff1a;每次传输一个字 程…

【C++】日期类Date(详解)

&#x1f525;个人主页&#xff1a;Forcible Bug Maker &#x1f525;专栏&#xff1a;C 目录 前言 日期类 日期类实现地图 获取某年某月的天数&#xff1a;GetMonthDay 检查日期合法&#xff0c;构造函数&#xff0c;拷贝构造函数&#xff0c;赋值运算符重载及析构函数…

【数据结构2-线性表】

数据结构2-线性表 1 线性表-数组2 线性表-单链式结构2.1 前插顺序单链表2.2 后插顺序单链表2.3 循环单链表2.4 双向链表 总结 线性表、栈、队列、串和数组都属于线性结构。 线性结构的基本特点是除第一个元素无直接前驱&#xff0c;最后一个元素无直接后继之外&#xff0c;其他…

.net反射(Reflection)

文章目录 一.概念&#xff1a;二.反射的作用&#xff1a;三.代码案例&#xff1a;四.运行结果&#xff1a; 一.概念&#xff1a; .NET 反射&#xff08;Reflection&#xff09;是指在运行时动态地检查、访问和修改程序集中的类型、成员和对象的能力。通过反射&#xff0c;你可…

【游戏专区】飞机大战

打过飞机的人都知道&#xff0c;不是那么好打滴&#xff0c;求得麻袋&#xff0c;甩掉你那脑子里的黄色信息。活不多说&#xff0c;我们开始吧。 1、easyX的原理 基于Windows图形编程&#xff0c;将Windows下的复杂程序过程进行封装&#xff0c;仅给用户提供一个简单熟悉的接…

21.组件组成

组件组成 组件最大的优势就是可复用性 当使用构建步骤时&#xff0c;我们一般会将 Vue 组件定义在一个单独的 .vue 文件中&#xff0c;这被叫做单文件组件(简称 SFC) 组件组成结构 <template><div>承载标签</div> </template> <script> expor…

【软件测试】正交表测试例题

【软件测试】正交表测试 例题1答案 例题2答案 例题3答案 例题1 很多Word编辑器都有字体修饰功能&#xff0c;可以将一个字加粗、倾斜、以及加上下划线。一个字可以同时被加粗和倾斜&#xff0c;也可以同时被倾斜和加下划线。三种因子Bold, Italic, Underline的效果可以任意组合…

计算机组成原理【CO】Ch3 存储系统

文章目录 考纲3.1 存储系统概述3.2 主存储器3.3 主存储器与CPU的连接3.4 外部存储器3.5 高速缓冲存储器3.6 虚拟存储器 【※】存储系统总体流程图【※】各个部件的存储位置计算机存储相关硬件与数据结构说明进程控制块&#xff08;PCB&#xff09;页表页表始址页表始址寄存器&a…

传感器融合 | 适用于自动驾驶场景的激光雷达传感器融合项目_将激光雷达的高分辨率成像+测量物体速度的能力相结合

项目应用场景 面向自动驾驶场景的激光雷达传感器融合&#xff0c;将激光雷达的高分辨率成像测量物体速度的能力相结合&#xff0c;项目是一个从多个传感器获取数据并将其组合起来的过程&#xff0c;可以更加好地进行环境感知。项目支持 ubuntu、mac 和 windows 平台。 项目效果…

CGLIB动态代理

文章目录 前言概要SpringBoot中使用小结 前言 当我们需要在Java中实现动态代理时&#xff0c;通常会考虑使用 JDK原生动态代理 或者 CGLIB动态代理。 我这里说一下CGLIB动态代理&#xff0c;并给出一个例子。 概要 CGLIB&#xff08;Code Generation Library&#xff09;是一…