算法总结篇 —— 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.hqwc.cn/news/651184.html

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

相关文章

不使用加减运算符实现整数加和减

文章目录 进位 进位 加粗 最近想出了不适用运算符实现加与减 首先按位与找出的是需不需要进位 按位与是两边同时为1,则为1,那么如果两边同时为1的话,是不是就该进位?所以我们用按位与来判断是否需要进位 然后再按位异或找出不同的位数 按位异或是两边不相等,也就是1 和 0的时…

项目实战:Qt获取CTP量化交易接口测试数据工具 v1.0.0(获取深度行情数据、订阅取消订阅)

若该文为原创文章&#xff0c;转载请注明出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/137937666 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

抽象的代理模式1.0版本

前言&#xff1a; 在阅读Spring Security官方文档时&#xff0c;里面设计到了一种设计模式——代理模式Proxy 众里寻她千百度&#xff0c;蓦然回首&#xff0c;那人却在灯火阑珊处 开始 在之前的文章里陈述了一个观点——编程语言和语言没有区别 现看看我们日常生活中的代理…

Java之复制图片

从文件夹中复制图片 从这个文件夹&#xff1a; 复制到这个空的文件夹&#xff1a; 代码如下&#xff1a; import java.io.*; import java.util.Scanner;/*** 普通文件的复制*/public class TestDome10 {public static void main(String[] args) {// 输入两个路径// 从哪里(源路…

智慧养猪场视频AI智能监控与可视化管理方案

在科技日新月异的今天&#xff0c;智能化、自动化已成为众多行业追求的方向。养猪业作为传统农业的重要组成部分&#xff0c;同样迎来了技术革新的春风。特别是随着人们对食品安全等问题的日益关注&#xff0c;养猪场视频监控监管方案的智能化升级显得尤为重要。 养猪场视频智…

用过最佳的wordpress模板

西瓜红&#xff0c;作为一种充满活力和激情的颜色&#xff0c;总是能给人留下深刻的印象。当这种鲜艳的色彩与经典的设计元素相结合时&#xff0c;就能打造出一款既时尚又实用的WordPress企业模板。今天&#xff0c;我们向您隆重推荐这款西瓜红经典配色WordPress企业模板。 这…

18种WEB常见漏洞:揭秘网络安全的薄弱点

输入验证漏洞: 认证和会话管理漏洞: 安全配置错误: 其他漏洞: 防范措施: Web 应用程序是现代互联网的核心&#xff0c;但它们也容易受到各种安全漏洞的影响。了解常见的 Web 漏洞类型&#xff0c;对于开发人员、安全测试人员和普通用户都至关重要。以下将介绍 18 种常见的 …

最受站长欢迎的wordpress模板

蓝色与黄色&#xff0c;作为经典的互补色&#xff0c;它们在企业网站设计中总能碰撞出令人印象深刻的火花。当这两种鲜艳的色彩巧妙结合时&#xff0c;不仅能够吸引访客的注意力&#xff0c;还能传达出一种活力四射、积极向上的企业形象。 今天&#xff0c;我们为您推荐的这款…

vue实现周日历 日历按周切换 vue日程管理

实现的功能 1、点击今天&#xff1a;回到今日日期并选中今日日期&#xff0c;查当天数据 2、点击左箭头&#xff1a;切换上一周 3、点击右箭头&#xff1a;切换下一周 4、黄圆圈代表有日程提醒&#xff0c;点击选中&#xff0c;下方对应显示当前日程提醒的内容&#xff0c;没有…

【论文笔记 | 异步联邦】PORT:How Asynchronous can Federated Learning Be?

1. 论文信息 How Asynchronous can Federated Learning Be?2022 IEEE/ACM 30th International Symposium on Quality of Service (IWQoS). IEEE, 2022&#xff0c;不属于ccf认定 2. introduction 2.1. 背景&#xff1a; 现有的异步FL文献中设计的启发式方法都只反映设计空…

c++输入年月日,计算是该年的第几天

直接把每个月的天数存到数组直接相加会很方便&#xff0c;不用过多的判断。 注意&#xff1a;润年判断方法&#xff08;可以被4整除但不能被100整除&#xff0c;或者可以被400整除&#xff09; #include <iostream> using namespace std;int main() {int year,month,da…

如何在vue3+vite中优雅的使用iconify图标

前言 从Vue2迁移到Vue3&#xff0c;在使用上有着很大的差别。本文的话主要是针对图标的使用差别上进行分析&#xff0c;同时给出基于iconify图标库中unplugin-icons的用法。这里特殊说明一下&#xff1a;其实element-plus中用到的图标也是基于iconify图标库的&#xff0c;在我们…