当前位置: 首页 > news >正文

DFS+回溯+剪枝(深度优先搜索)——搜索算法

目录

一、递归

1.什么是递归?

2.什么时候使用递归?

3.如何理解递归?

4.如何写好递归?

二、记忆化搜索(记忆递归)

三、回溯

四、剪枝

五、综合试题 

1.N皇后

2.解数独 


        DFS也就是深度优先搜索,比如二叉树的前,中,后序遍历都属于DFS。其本质是递归,要学好DFS首先需要掌握递归。接下来咱们就一起来学习DFS涉及的算法。

一、递归

1.什么是递归?

递归可以这样理解把它拆分出来,两个字,“递”和“归”
递推这就需要找到递推公式
回归需要找到回归条件,递推过程逐渐逼近回归条件

直白一点来说就是,一个函数自己调用自己的情况,当然一定是要能够返回的。

二叉树的遍历,快排,归并中都用到了递归。

2.什么时候使用递归?

        满足以下条件通常都能使用递归解决:在主问题中能找到相同的子问题,在子问题中又能找到相同的子问题。

        注意:递归过程,也就是在前一个函数没有销毁的时候调用下一个相同函数,调用函数就需要开辟函数栈帧,会占用大量空间,所以递归层数太多会导致栈溢出,解决不了问题。

        缺点:递归算法会占用大量内存,有栈溢出的风险,在实际开发中要尽量减少递归算法的使用。

        优点:递归算法简单明了,容易被想到,代码也是非常的好写。

3.如何理解递归?

        在开始学递归时还是需要去分析递归展开细节图的,这个可以让我们理解递归的工作原理,理解为什么递归能解决问题。当我们在逻辑上有了自洽后。就再也不要去管递归展开图,如果一味地去纠结递归展开图,只会让我们越来越晕。

        相反,我们需要从宏观的角度看待递归问题,把递归函数看作一个黑盒并且相信这个黑盒一定能够帮我们完成任务。

4.如何写好递归?

写好递归只需要做好下面这几步:

  • 1.找到相同的子问题 --> 解决函数头的设计。
  • 2.只关心某个子问题是如何解决的 --> 解决函数体的书写。
  • 3.处理递归函数的出口 --> 返回值的确定。

        我们可以知道相同的子问题中的“相同”指的是逻辑相同,而不同的只有操作对象,所以在设计函数头的参数列表时只需要让这些不同的操作对象能够参入即可。

所以我们做这样一个函数头:

void _hanota(vector<int>& A, vector<int>& B, vector<int>& C,int n);

表示把A柱中n个盘子移动到C柱,B是辅助用的柱子。

        提示:这里大家可能会有一个疑惑,为什么传入的参数是几个盘子,而不是哪几个盘子。其实是因为游戏规则本来就是只能从上往下依次从柱子取盘。 所以知道要取一个盘子也就能确定哪几个盘子,它们是一对一的关系。

单个子问题的解决我放在代码中讲解,如下:

class Solution {
public:void _hanota(vector<int>& A, vector<int>& B, vector<int>& C,int n){if(n==1)//只需要移动一个盘的时候直接操作{C.push_back(A.back());A.pop_back();return;}//先把A中n-1个盘移动到B中_hanota(A,C,B,n-1);//在把剩下一个盘移动到C中C.push_back(A.back());A.pop_back();//最后再把B中的n-1个盘移动到C中_hanota(B,A,C,n-1);}void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {//题目通过的参数列表无法满足我们的需求,重写一个函数来解决。_hanota(A,B,C,A.size());}
};

二、记忆化搜索(记忆递归)

通过下面这个题我会引出记忆化搜索。

以上是一个爬楼梯问题,我们通过找规律来解决问题。

楼顶数:1        2        3        4        5        6

方法数:1        2        3        5        8        13

通过观察发现楼顶数x与方法数F( x )的关系为

出现这样一个递推公式我们第一想到的就是递归来实现。

1.递归代码:

int F(int n)
{if(n<=2) return n;else return F(n-1)+F(n-2);
}

注意:这里为了方便说明问题函数名我直接使用F,这和原题提供的函数名不一样。

下面是对以上代码的递归展开图进行剖析:

红线为递推过程,绿线为回归过程。

接下来是复杂度分析

时间复杂度为O(2^n),空间复杂度为O(n)

        通过观察我们发现出现很多重复计算的地方(图中画圈颜色相同的地方)如果减少这些重复计算的地方那么效率会提高很多。为了解决这个问题,我们想象一下,把每次计算的数据存起来,下次用到的时候就不用计算,直接返回。而这就是记忆递归。

2.记忆递归

int arr[46]={0};//通过题目确定数据范围
F(int n)
{if(n<=2) return n;if(arr[n]!=0) return arr[n];else return arr[n]=F(n-1)+F(n-2);
}

        创建一个数组并初始值为零,把每次返回的值存在数组里,这样可以避免重复计算,判断a[n]为非0则直接返回。时间复杂度为O(n),空间复杂度为O(n)。

通常能使用记忆递归解决的问题都能转化为动态规划,如下:

3.动态规划

int F(int n){int dp[46]={0};dp[1]=1,dp[2]=2;for(int i=3;i<n+1;i++)dp[i]=dp[i-1]+dp[i-2];return dp[n];
}

        把1到n,每个楼顶对应的方法数存入数组中,用前两个来计算后一个,直到推到n,此方法相比以上方法,减少了递归带来的内存申请,时间复杂度为O(n),空间复杂度为O(1)。

好题推荐:329. 矩阵中的最长递增路径 - 力扣(LeetCode)

三、回溯

        回溯又叫作“恢复现场”,它是基于递归的一种算法,是为了解决搜索时路径之间的信息互相干扰的问题。如下:

回溯的具体用法,我们来从下面这个题中感受。 

        在做搜索题的时候最重要的莫过于就是决策树的设计。如果决策树做得清晰明了,那么代码也就好写了。

        什么是决策树?在搜索过程中它抽象出来的必定是一棵树形结构,而这棵树是如何展开的,我们在设计展开逻辑的过程,也就是在做一颗决策树。如下:

class Solution {
public:vector<vector<int>> ret;//统计结果vector<int> path;//记录路径vector<vector<int>> subsets(vector<int>& nums){dfs(nums,0);return ret;}void dfs(vector<int>& nums,int pos){if(pos==nums.size())//即到达叶子节点{ret.push_back(path);return;}//不选该元素直接进入下一元素的选择dfs(nums,pos+1);//选择该元素并进入下一元素的选择path.push_back(nums[pos]);dfs(nums,pos+1);//函数退出之前先把该层的数据清除(回溯)path.pop_back();}
};

其实这里回溯思想就一句代码,即path.pop_back()。回溯就这么简单。

我们看一看没有回溯的决策树

这里以叶子节点3开始画回归路线蓝线:回归红线:递推

        我们可以看到如果不把[3]这一节点的信息清除的话它会把信息带到上一层,然后一直往下带,每一节点都不恢复现场,就会使每个节点都带上一个信息往回传,导致结果错误。所以回溯算法在很多场景都是至关重要的。

当然决策树并不是唯一的,每个人画的可能都不一样,比如还可以这样:

不同的决策树代码也是不同的,如下:

class Solution
{
public:vector<vector<int>> ret;vector<int> path;vector<vector<int>> subsets(vector<int>& nums){_subsets(nums,0);return ret;}void _subsets(const vector<int>& nums,int pos){ret.push_back(path);for(int i=pos;i<nums.size();i++){path.push_back(nums[i]);_subsets(nums,i+1);path.pop_back();//恢复现场}}
};

四、剪枝

        剪枝可以这么理解,如果我们已知某条枝干没有正确答案或某条枝干是错误的,那么我们就不进行搜索,这样可以减少不必要的搜索,提高效率。具体我们可以从题中感受。

这个题放在小学数学就是一个画树状图的题,我们直接开始吧。 

        如上这颗决策树,在一条路径中如果一个元素选过一次,那么下次就不能再选,需要把它剪掉。我们可以使用一个哈希表来记录某个元素是否出现过。但在该过程中同样需要注意“恢复现场”。

class Solution {
public:vector<int> path;vector<vector<int>> ret;int n;vector<vector<int>> permute(vector<int>& nums){n = nums.size();vector<bool> hash(n);dfs(nums,hash);return ret;}void dfs(vector<int>& nums,vector<bool>& hash){if(path.size()==n){ret.push_back(path);return;}for(int i=0;i<n;i++){if(hash[i]) continue;//剪枝hash[i]=true;path.push_back(nums[i]);dfs(nums,hash);hash[i]=false;path.pop_back();}}
};

同样的这里剪枝就一句代码,即 if(hash[i]) continue。剪枝就这么简单。 

五、综合试题 

1.N皇后

首先我们还是一样的试着把决策树画出来,如下: 

这样我们可以知道这个题解题框架。接下来就是处理如何剪枝的问题。

        题目要求一个棋子的横排,竖排,斜对角, 反斜对角都不能有其他棋子,那么这就好办,只需要使用4个哈希表来记录这些位置是否已有棋子,如果有那就不能放,直到遍历完所以格子还是无法将棋子放入,则该条路径行不通。

class Solution {
public:vector<vector<string>> ret;vector<string> path;vector<bool>  row,col,bias1,bias2;vector<vector<string>> solveNQueens(int n){row.resize(n,false),col.resize(n,false);bias1.resize(2*n-1,false),bias2.resize(2*n-1,false);for(int i=0;i<n;i++) path.push_back(string(n,'.'));dfs(0,n);return ret;}void dfs(int pos,int n){if(pos==n){ret.push_back(path);return;}for(int j=0;j<n;j++){//剪枝if(row[pos]||col[j]||bias1[pos+j]||bias2[n-pos+j-1]) continue;row[pos]=col[j]=bias1[pos+j]=bias2[n-pos+j-1]=true;path[pos][j]='Q';dfs(pos+1,n);//恢复现场(回溯)row[pos]=col[j]=bias1[pos+j]=bias2[n-pos+j-1]=false;path[pos][j]='.';}}
};

2.解数独 

        同样我们需要画出决策树,我们可以直接暴力搜索每一个空缺的位置,再在每一个空位暴力枚举每一个数字即可。如下:

        在此过程中我们需要注意剪枝与回溯的问题,为了检查数字的合法性,还需要我们记录每一行,每一列,每个3*3小方格中的某个数字是否出现过。所以需要3个哈希表。

由题可知行数列数都是固定的,为9行9列。

        所以可以用   bool row[9][10]     bool col[9][10]来作为哈希表记录某一行的某个数字是否出现过。

使用bool hash[3][3][10],来作为哈希表记录某一个3*3宫格的某个数字是否出现过。

代码示例:

class Solution
{
public:bool row[9][10],col[9][10],hash[3][3][10];void solveSudoku(vector<vector<char>>& board){//初始化哈希表for(int i=0;i<9;i++){for(int j=0;j<9;j++){if(board[i][j]=='.') continue;int key=board[i][j]-'0';row[i][key]=col[j][key]=hash[i/3][j/3][key]=true;}}dfs(board);}bool dfs(vector<vector<char>>& board){for(int i=0;i<9;i++){for(int j=0;j<9;j++){if(board[i][j]!='.') continue;for(int key=1;key<=9;key++){if(row[i][key]||col[j][key]||hash[i/3][j/3][key]) continue;//剪枝board[i][j]=key+'0';row[i][key]=col[j][key]=hash[i/3][j/3][key]=true;if(dfs(board)) return true;//剪枝//恢复现场board[i][j]='.';row[i][key]=col[j][key]=hash[i/3][j/3][key]=false;}return false;}}return true;}
};

好题推荐:

​​​​​​39. 组合总和 - 力扣(LeetCode)

22. 括号生成 - 力扣(LeetCode)

1219. 黄金矿工 - 力扣(LeetCode)

77. 组合 - 力扣(LeetCode)

相关文章:

DFS+回溯+剪枝(深度优先搜索)——搜索算法

目录 一、递归 1.什么是递归&#xff1f; 2.什么时候使用递归&#xff1f; 3.如何理解递归&#xff1f; 4.如何写好递归&#xff1f; 二、记忆化搜索&#xff08;记忆递归&#xff09; 三、回溯 四、剪枝 五、综合试题 1.N皇后 2.解数独 DFS也就是深度优先搜索&am…...

在cursor/vscode中使用godot C#进行游戏开发

要在 Visual Studio Code(VS Code)中启动 C#Godot 项目&#xff0c;可以按照以下步骤进行配置&#xff1a; 1.安装必要的工具 • 安装 Visual Studio Code&#xff1a;确保你已经安装了最新版本的 VS Code。 • 安装.NET SDK&#xff1a;下载并安装.NET 7.x SDK&#xff08;…...

vant4 van-list组件的使用

<van-listv-if"joblist && joblist.length > 0"v-model:loading"loading":finished"finished":immediate-check"false"finished-text"没有更多了"load"onLoad">// 加载 const loading ref(fals…...

介绍 Liquibase、Flyway、Talend 和 Apache NiFi:选择适合的工具

在现代软件开发中&#xff0c;尤其是在数据库管理和数据集成方面&#xff0c;选择合适的工具至关重要。本文将介绍四个流行的工具&#xff1a;Liquibase、Flyway、Talend 和 Apache NiFi&#xff0c;分析它们的应用、依赖以及如何选择适合的工具。 1. Liquibase 简介&#xff…...

攻防世界33 catcat-new【文件包含/flask_session伪造】

题目&#xff1a; 点击一只猫猫&#xff1a; 看这个url像是文件包含漏洞&#xff0c;试试 dirsearch扫出来/admin&#xff0c;访问也没成功&#xff08;--delay 0.1 -t 5&#xff09; 会的那几招全用不了了哈哈&#xff0c;那就继续看答案 先总结几个知识点 1./etc/passwd&am…...

Git -> Git配置密钥对,并查看公钥

Git密钥对的核心作用 私钥 (id_rsa) 你的数字身份证&#xff1a;存放在本机 ~/.ssh 目录下必须严格保密&#xff08;类似银行卡密码&#xff09;&#xff0c;不可泄露或共享用于 解密 来自服务器的加密信息 公钥 (id_rsa.pub) 可公开的验证锁&#xff1a;需要上传到 Git 服…...

淘宝订单列表Fragment转场动画卡顿解决方案

如何应对产品形态与产品节奏相对确定情况下转变为『在业务需求与产品形态高度不确定性的情况下&#xff0c;如何实现业务交付时间与交付质量的确定性』。我们希望通过混合架构&#xff08;Native 业务容器 Weex 2.0&#xff09;作为未来交易终端架构的重要演进方向&#xff0c…...

【ESP32指向鼠标】——icm20948与esp32通信

【ESP32指向鼠标】——icm20948与esp32通信 ICM-20948介绍 ICM-20948 是一款由 InvenSense&#xff08;现为 TDK 的一部分&#xff09;生产的 9 轴传感器集成电路。它结合了 陀螺仪、加速度计和磁力计。 内置了 DMP&#xff08;Digital Motion Processor&#xff09;即负责执…...

Xcode证书密钥导入

证书干嘛用 渠道定期会给xcode证书&#xff0c;用来给ios打包用&#xff0c;证书里面有记录哪些设备可以打包进去。 怎么换证书 先更新密钥 在钥匙串访问中&#xff0c;选择系统。(选登录也行&#xff0c;反正两个都要导入就是了)。 mac中双击所有 .p12 后缀的密钥&#xff…...

Ubuntu安装PgSQL17

参考官网教程&#xff0c;Ubuntu24 apt在线安装Postgres 17 1. 要手动配置 Apt 存储库 # 导入存储库签名密钥&#xff1a; sudo apt install curl ca-certificates sudo install -d /usr/share/postgresql-common/pgdg sudo curl -o /usr/share/postgresql-common/pgdg/apt…...

K8S容器启动提示:0/2 nodes are available: 2 Insufficient cpu.

问题&#xff1a;K8S的容器启动报错0/2 nodes are available: 2 Insufficient cpu. 原因&#xff1a;Pod的资源请求&#xff08;requests&#xff09;设置不当&#xff1a;在Kubernetes中&#xff0c;调度器根据Pod的requests字段来决定哪个节点可以运行该Pod。如果一个Pod声明…...

LabVIEW外腔二极管激光器稳频实验

本项目利用LabVIEW软件开发了一个用于外腔二极管激光器稳频实验的系统。系统能够实现激光器频率的稳定控制和实时监测&#xff0c;为激光实验提供了重要支持。 项目背景&#xff1a; 系统解决了外腔二极管激光器频率不稳定的问题&#xff0c;以满足对激光器频率稳定性要求较高…...

笔记6——字典dict(dictionary)

文章目录 字典dict(dictionary)定义特点常用操作1.访问值2.添加键值对3.修改值4.删除键值对5.遍历字典6.合并字典 性能应用场景dict和list的区别 字典dict(dictionary) 以 键 - 值对 (key - value pairs)的形式存储数据 定义 字典使用花括号 {} 来定义&#xff0c;键和值之…...

【MySQL】InnoDB单表访问方法

目录 1、背景2、环境3、访问类型【1】const【2】ref【3】ref_or_null【4】range【5】index【6】all 4、总结 1、背景 mysql通过查询条件查询到结果的过程就叫访问方法&#xff0c;一条查询语句的访问方法有很多种&#xff0c;接下来我们就来讲一下各种访问方法。 2、环境 创…...

APP端网络测试与弱网模拟!

当前APP网络环境比较复杂&#xff0c;网络制式有2G、3G、4G网络&#xff0c;还有越来越多的公共Wi-Fi。不同的网络环境和网络制式的差异&#xff0c;都会对用户使用app造成一定影响。另外&#xff0c;当前app使用场景多变&#xff0c;如进地铁、上公交、进电梯等&#xff0c;使…...

【个人开发】deepseed+Llama-factory 本地数据多卡Lora微调

文章目录 1.背景2.微调方式2.1 关键环境版本信息2.2 步骤2.2.1 下载llama-factory2.2.2 准备数据集2.2.3 微调模式2.2.4 微调脚本 2.3 踩坑经验2.3.1 问题一&#xff1a;ValueError: Undefined dataset xxxx in dataset_info.json.2.3.2 问题二&#xff1a; ValueError: Target…...

Redis7.0八种数据结构底层原理

导读 本文介绍redis应用数据结构与物理存储结构,共八种应用数据结构和 一. 内部数据结构 1. sds sds是redis自己设计的字符串结构有以下特点: jemalloc内存管理预分配冗余空间二进制安全(c原生使用\0作为结尾标识,所以无法直接存储\0)动态计数类型(根据字符串长度动态选择…...

Kafka 高吞吐量的底层技术原理

Kafka 之所以能够实现高吞吐量&#xff08;每秒百万级消息处理&#xff09;&#xff0c;主要依赖于其底层设计和多项优化技术。以下是 Kafka 实现高吞吐量的关键技术原理&#xff1a; 1. 顺序读写磁盘 Kafka 利用磁盘的顺序读写特性&#xff0c;避免了随机读写的性能瓶颈。 顺…...

CCFCSP第34次认证第一题——矩阵重塑(其一)

第34次认证第一题——矩阵重塑&#xff08;其一&#xff09; 官网链接 时间限制&#xff1a; 1.0 秒 空间限制&#xff1a; 512 MiB 相关文件&#xff1a; 题目目录&#xff08;样例文件&#xff09; 题目背景 矩阵&#xff08;二维&#xff09;的重塑&#xff08;reshap…...

网络工程师 (35)以太网通道

一、概念与原理 以太网通道&#xff0c;也称为以太端口捆绑、端口聚集或以太链路聚集&#xff0c;是一种将多个物理以太网端口组合成一个逻辑通道的技术。这一技术使得多个端口能够并行工作&#xff0c;共同承担数据传输任务&#xff0c;从而提高了网络的传输能力和可靠性。 二…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

vulnyx Blogger writeup

信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面&#xff0c;gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress&#xff0c;说明目标所使用的cms是wordpress&#xff0c;访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...