回溯里面的基本概念

1.深度优先遍历和深度优先搜索(DFS)

  • 深度优先遍历

        主要思路是从图中一个未访问的顶点 V 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底...,不断递归重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走。树是图的一种特例(连通无环的图就是树),接下来我们来看看树用深度优先遍历该怎么遍历。

1、我们从根节点 1 开始遍历,它相邻的节点有 2,3,4,先遍历节点 2,再遍历 2 的子节点 5,然后再遍历 5 的子节点 9。

2、上图中一条路已经走到底了(9是叶子节点,再无可遍历的节点),此时就从 9 回退到上一个节点 5,看下节点 5 是否还有除 9 以外的节点,没有继续回退到 2,2 也没有除 5 以外的节点,回退到 1,1 有除 2 以外的节点 3,所以从节点 3 开始进行深度优先遍历,如下:

3、同理从 10 开始往上回溯到 6, 6 没有除 10 以外的子节点,再往上回溯,发现 3 有除 6 以外的子点 7,所以此时会遍历 7。

3、从 7 往上回溯到 3, 1,发现 1 还有节点 4 未遍历,所以此时沿着 4, 8 进行遍历,这样就遍历完成了。
完整的节点的遍历顺序如下(节点上的的蓝色数字代表):

相信大家看到以上的遍历不难发现这就是树的前序遍历,实际上不管是前序遍历,还是中序遍历,亦或是后序遍历,都属于深度优先遍历。


事实上,深度优先搜索和深度优先遍历本质是同一个东西,只不过

  • 遍历是形式,目的是搜索 

2.宽度优先遍历和宽度优先搜索(BFS)

  • 宽度优先遍历

宽度优先遍历,指的是从图的一个未遍历的节点出发,先遍历这个节点的相邻节点,再依次遍历每个相邻节点的相邻节点。

上文所述树的广度优先遍历动图如下,每个节点的值即为它们的遍历顺序。所以广度优先遍历也叫层序遍历,先遍历第一层(节点 1),再遍历第二层(节点 2,3,4),第三层(5,6,7,8),第四层(9,10)。

深度优先遍历用的是栈,而广度优先遍历要用队列来实现,我们以下图二叉树为例来看看如何用队列来实现广度优先遍历。

 

事实上,宽度优先遍历和宽度优先搜索本质上是一样的。

只不过

  • 遍历是形式,目的是搜索 

3.暴搜

其实搜索就是暴搜。

暴搜分为两种情况:DFS和BFS。

4.回溯

 回溯算法(backtracking algorithm)是一种通过穷举来解决问题的方法,它的核心思想是从一个初始状态出发,暴力搜索所有可能的解决方案,当遇到正确的解则将其记录,直到找到解或者尝试了所有可能的选择都无法找到解为止。

回溯算法通常采用“深度优先搜索”来遍历解空间。在“二叉树”章节中,我们提到前序、中序和后序遍历都属于深度优先搜索。接下来,我们利用前序遍历构造一个回溯问题,逐步了解回溯算法的工作原理。

例如:

  • 例题一

给定一棵二叉树,搜索并记录所有值为 7 的节点,请返回节点列表。

对于此题,我们前序遍历这棵树,并判断当前节点的值是否为 7 ,若是,则将该节点的值加入结果列表 res 之中。 

/* 前序遍历:例题一 */
void preOrder(TreeNode *root) {if (root == nullptr) {return;}if (root->val == 7) {// 记录解res.push_back(root);}preOrder(root->left);preOrder(root->right);
}

之所以称之为回溯算法,是因为该算法在搜索解空间时会采用“尝试”与“回退”的策略。当算法在搜索过程中遇到某个状态无法继续前进或无法得到满足条件的解时,它会撤销上一步的选择,退回到之前的状态,并尝试其他可能的选择。

对于例题一,访问每个节点都代表一次“尝试”,而越过叶节点或返回父节点的 return 则表示“回退”。

值得说明的是,回退并不仅仅包括函数返回。为解释这一点,我们对例题一稍作拓展。

  • 例题二

在二叉树中搜索所有值为 7 的节点,请返回根节点到这些节点的路径

 在例题一代码的基础上,我们需要借助一个列表 path 记录访问过的节点路径。当访问到值为 7 的节点时,则复制 path 并添加进结果列表 res 。遍历完成后,res 中保存的就是所有的解。代码如下所示:

/* 前序遍历:例题二 */
void preOrder(TreeNode *root) {if (root == nullptr) {return;}// 尝试path.push_back(root);if (root->val == 7) {// 记录解res.push_back(path);}preOrder(root->left);preOrder(root->right);// 回退path.pop_back();
}

在每次“尝试”中,我们通过将当前节点添加进 path 来记录路径;而在“回退”前,我们需要将该节点从 path 中弹出,以恢复本次尝试之前的状态

观察下图所示的过程,我们可以将尝试和回退理解为“前进”与“撤销”,两个操作互为逆向。

 

 

5.剪枝

复杂的回溯问题通常包含一个或多个约束条件,约束条件通常可用于“剪枝”

  • 例题三

在二叉树中搜索所有值为 7 的节点,请返回根节点到这些节点的路径,并要求路径中不包含值为 3 的节点

为了满足以上约束条件,我们需要添加剪枝操作:在搜索过程中,若遇到值为 3 的节点,则提前返回,不再继续搜索。代码如下所示:

/* 前序遍历:例题三 */
void preOrder(TreeNode *root) {// 剪枝if (root == nullptr || root->val == 3) {return;}// 尝试path.push_back(root);if (root->val == 7) {// 记录解res.push_back(path);}preOrder(root->left);preOrder(root->right);// 回退path.pop_back();
}

“剪枝”是一个非常形象的名词。如图所示,在搜索过程中,我们“剪掉”了不满足约束条件的搜索分支,避免许多无意义的尝试,从而提高了搜索效率。

6.框架代码

接下来,我们尝试将回溯的“尝试、回退、剪枝”的主体框架提炼出来,提升代码的通用性。

在以下框架代码中,state 表示问题的当前状态,choices 表示当前状态下可以做出的选择:

 

/* 回溯算法框架 */
void backtrack(State *state, vector<Choice *> &choices, vector<State *> &res) {// 判断是否为解if (isSolution(state)) {// 记录解recordSolution(state, res);// 不再继续搜索return;}// 遍历所有选择for (Choice choice : choices) {// 剪枝:判断选择是否合法if (isValid(state, choice)) {// 尝试:做出选择,更新状态makeChoice(state, choice);backtrack(state, choices, res);// 回退:撤销选择,恢复到之前的状态undoChoice(state, choice);}}
}

 接下来,我们基于框架代码来解决例题三。状态 state 为节点遍历路径,选择 choices 为当前节点的左子节点和右子节点,结果 res 是路径列表:

/* 判断当前状态是否为解 */
bool isSolution(vector<TreeNode *> &state) {return !state.empty() && state.back()->val == 7;
}/* 记录解 */
void recordSolution(vector<TreeNode *> &state, vector<vector<TreeNode *>> &res) {res.push_back(state);
}/* 判断在当前状态下,该选择是否合法 */
bool isValid(vector<TreeNode *> &state, TreeNode *choice) {return choice != nullptr && choice->val != 3;
}/* 更新状态 */
void makeChoice(vector<TreeNode *> &state, TreeNode *choice) {state.push_back(choice);
}/* 恢复状态 */
void undoChoice(vector<TreeNode *> &state, TreeNode *choice) {state.pop_back();
}/* 回溯算法:例题三 */
void backtrack(vector<TreeNode *> &state, vector<TreeNode *> &choices, vector<vector<TreeNode *>> &res) {// 检查是否为解if (isSolution(state)) {// 记录解recordSolution(state, res);}// 遍历所有选择for (TreeNode *choice : choices) {// 剪枝:检查选择是否合法if (isValid(state, choice)) {// 尝试:做出选择,更新状态makeChoice(state, choice);// 进行下一轮选择vector<TreeNode *> nextChoices{choice->left, choice->right};backtrack(state, nextChoices, res);// 回退:撤销选择,恢复到之前的状态undoChoice(state, choice);}}
}

根据题意,我们在找到值为 7 的节点后应该继续搜索,因此需要将记录解之后的 return 语句删除。图对比了保留或删除 return 语句的搜索过程。 

相比基于前序遍历的代码实现,基于回溯算法框架的代码实现虽然显得啰唆,但通用性更好。实际上,许多回溯问题可以在该框架下解决。我们只需根据具体问题来定义 state 和 choices ,并实现框架中的各个方法即可。 

7.常用术语

为了更清晰地分析算法问题,我们总结一下回溯算法中常用术语的含义,并对照例题三给出对应示例,如表 所示。

常见的回溯算法术语

名词定义例题三
解(solution)解是满足问题特定条件的答案,可能有一个或多个根节点到节点 7 的满足约束条件的所有路径
约束条件(constraint)约束条件是问题中限制解的可行性的条件,通常用于剪枝路径中不包含节点 3
状态(state)状态表示问题在某一时刻的情况,包括已经做出的选择当前已访问的节点路径,即 path 节点列表
尝试(attempt)尝试是根据可用选择来探索解空间的过程,包括做出选择,更新状态,检查是否为解递归访问左(右)子节点,将节点添加进 path ,判断节点的值是否为 7
回退(backtracking)回退指遇到不满足约束条件的状态时,撤销前面做出的选择,回到上一个状态当越过叶节点、结束节点访问、遇到值为 3 的节点时终止搜索,函数返回
剪枝(pruning)剪枝是根据问题特性和约束条件避免无意义的搜索路径的方法,可提高搜索效率当遇到值为 3 的节点时,则不再继续搜索

Tip

问题、解、状态等概念是通用的,在分治、回溯、动态规划、贪心等算法中都有涉及。

8.优点与局限性

回溯算法本质上是一种深度优先搜索算法,它尝试所有可能的解决方案直到找到满足条件的解。这种方法的优点在于能够找到所有可能的解决方案,而且在合理的剪枝操作下,具有很高的效率。

然而,在处理大规模或者复杂问题时,回溯算法的运行效率可能难以接受

  • 时间:回溯算法通常需要遍历状态空间的所有可能,时间复杂度可以达到指数阶或阶乘阶。
  • 空间:在递归调用中需要保存当前的状态(例如路径、用于剪枝的辅助变量等),当深度很大时,空间需求可能会变得很大。

即便如此,回溯算法仍然是某些搜索问题和约束满足问题的最佳解决方案。对于这些问题,由于无法预测哪些选择可生成有效的解,因此我们必须对所有可能的选择进行遍历。在这种情况下,关键是如何优化效率,常见的效率优化方法有两种。

  • 剪枝:避免搜索那些肯定不会产生解的路径,从而节省时间和空间。
  • 启发式搜索:在搜索过程中引入一些策略或者估计值,从而优先搜索最有可能产生有效解的路径。

9.回溯典型例题

回溯算法可用于解决许多搜索问题、约束满足问题和组合优化问题。

搜索问题:这类问题的目标是找到满足特定条件的解决方案。

  • 全排列问题:给定一个集合,求出其所有可能的排列组合。
  • 子集和问题:给定一个集合和一个目标和,找到集合中所有和为目标和的子集。
  • 汉诺塔问题:给定三根柱子和一系列大小不同的圆盘,要求将所有圆盘从一根柱子移动到另一根柱子,每次只能移动一个圆盘,且不能将大圆盘放在小圆盘上。

约束满足问题:这类问题的目标是找到满足所有约束条件的解。

  • n 皇后:在 n×n 的棋盘上放置 n 个皇后,使得它们互不攻击。
  • 数独:在 9×9 的网格中填入数字 1 ~ 9 ,使得每行、每列和每个 3×3 子网格中的数字不重复。
  • 图着色问题:给定一个无向图,用最少的颜色给图的每个顶点着色,使得相邻顶点颜色不同。

组合优化问题:这类问题的目标是在一个组合空间中找到满足某些条件的最优解。

  • 0-1 背包问题:给定一组物品和一个背包,每个物品有一定的价值和重量,要求在背包容量限制内,选择物品使得总价值最大。
  • 旅行商问题:在一个图中,从一个点出发,访问所有其他点恰好一次后返回起点,求最短路径。
  • 最大团问题:给定一个无向图,找到最大的完全子图,即子图中的任意两个顶点之间都有边相连。

请注意,对于许多组合优化问题,回溯不是最优解决方案。

  • 0-1 背包问题通常使用动态规划解决,以达到更高的时间效率。
  • 旅行商是一个著名的 NP-Hard 问题,常用解法有遗传算法和蚁群算法等。
  • 最大团问题是图论中的一个经典问题,可用贪心算法等启发式算法来解决。

 

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

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

相关文章

LeetCode 热题100(十五)【动态规划】(3)

15.7最长递增子序列&#xff08;中等&#xff09; 题目描述&#xff1a;leetcode链接 300. 最长递增子序列 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元…

springboot如何集成工作流审批流,流程设计器集成,业务表单和工作流绑定,详细步骤和实际案例参考(源码)

前言 activiti工作流引擎项目&#xff0c;企业erp、oa、hr、crm等企事业办公系统轻松落地&#xff0c;一套完整并且实际运用在多套项目中的案例&#xff0c;满足日常业务流程审批需求。 一、项目形式 springbootvueactiviti集成了activiti在线编辑器&#xff0c;流行的前后端…

02_Node.js模块化

02_Node.js模块化 知识点自测 以下代码运行的结果是多少&#xff1f; const arr [10, 20, 30] const result arr.map(val > val 1).reduce((sum, val) > sum val, 0) console.log(result) A&#xff1a;60 B&#xff1a;63 <details><summary>答案</…

vulnhub kioptirx1.2 超详细wp

探测 nmap --min-rate 10000 -p- 192.168.128.134 最小速率10000 nmap -sT -sV -sC -O 192.168.128.134 web打点 无弱口令 暴露cms寻找exp searchsploit LotusCMS -m 16982 [输入id号和参数m可以直接把东西复制到当前目录] 查看txt里面发现 都是xss没有rce github搜索到一个…

vulnhub靶场之【grotesque】三

前言 靶机&#xff1a;grotesque-3 192.168.1.44 攻击 &#xff1a;kali 192.168.1.16 都是虚拟机环境&#xff0c;桥接模式 主机发现 使用arp-scan -l或者netdiscover -r 192.168.1.1/24搜索 信息收集 使用nmap扫描 防止有遗漏&#xff0c;再扫描全端口 网站信息收集 …

大规模模型部署、推理的工具:Xinference

有没有 Xinference之前&#xff0c;如果想要部署应用一个开源模型&#xff0c;可能会面临以下一些情况和挑战&#xff1a; 自行开发推理框架&#xff1a; 需要投入大量的时间和精力来构建一个可靠且高效的推理框架&#xff0c;包括处理模型加载、资源管理、请求调度等复杂的任务…

C语言选择法排序

C语言编程&#xff0c;用选择法对数组中4个整数按由大到小排序 1、代码如下&#xff1a; #include<stdio.h> #include<math.h> #include<string.h>int main() {void sort(int array[],int n);printf("测试开始\n");int nums[] {2,3,4,1};sort(n…

SpringBoot的validation参数校验

文章目录 前言一、引入validation 依赖二、validation中的注解说明 &#xff08;1&#xff09;Validated&#xff08;2&#xff09;Valid&#xff08;3&#xff09;NotNull&#xff08;4&#xff09;NotBlank&#xff08;5&#xff09;NotEmpty&#xff08;6&#xff09;Patte…

Go的Gin比java的Springboot更加的开箱即用?

前言 隔壁组的云计算零零后女同事&#xff0c;后文简称 云女士 &#xff0c;非说 Go 的 Gin 框架比 Springboot 更加的开箱即用&#xff0c;我心想在 Java 里面 Springboot 已经打遍天下无敌手&#xff0c;这份底蕴岂是 Gin 能比。 但是云女士突出一个执拗&#xff0c;非我要…

docker学习笔记(四)--DockerFile

文章目录 一、什么是Dockerfile二、docker build命令三、dockerfile指令3.1 FROM3.2 ENV3.3 WORKDIR3.4 RUN3.5 CMD3.6 ENTRYPOINT3.7 EXPOSE3.8 ARG3.9 ADD3.10 COPY3.11 VOLUME 四、dockerfile示例 一、什么是Dockerfile Dockerfile 是用于构建 Docker 镜像的脚本文件&#…

撰写技术文档的关键步骤和核心要点

编写项目的技术文档是一个重要且细致的任务&#xff0c;它不仅有助于项目的当前开发团队理解系统的结构和工作原理&#xff0c;还为未来的维护和扩展提供了宝贵的参考资料。以下是撰写技术文档时应遵循的几个关键步骤和组成部分&#xff1a; 1. 概述 项目简介&#xff1a;简要…

Ant-Design-Vue 全屏下拉日期框无法显示,能显示后小屏又位置错乱

问题1&#xff1a;在全屏后 日期选择器的下拉框无法显示。 解决&#xff1a;在Ant-Design-Vue的文档中&#xff0c;很多含下拉框的组件都有一个属性 getPopupContainer可以用来指定弹出层的挂载节点。 在该组件上加上 getPopupContainer 属性,给挂载到最外层盒子上。 <temp…

【前端学习路线】(超详细版本)

先附上学习路线图&#xff1a;前端学习路线 第一阶段&#xff1a;前端入门&#xff08;htmlcss&#xff09; 前端最基本的知识&#xff0c;需要先将这些内容融汇贯通&#xff0c;学习后面内容才会不吃力。学习完可以做几个静态页练习一下。 推荐视频学习链接&#xff1a; 黑马程…

Vue生成类似于打卡页面

数据表格 <el-table :data"tableData" border height"calc(100vh - 240px)" :cell-style"cellFun"><el-table-column label"姓名" show-overflow-tooltip prop"name" align"center"/><el-table-co…

JVM学习《垃圾回收算法和垃圾回收器》

目录 1.垃圾回收算法 1.1 标记-清除算法 1.2 复制算法 1.3 标记-整理算法 1.4 分代收集算法 2.垃圾回收器 2.1 熟悉一下垃圾回收的一些名词 2.2 垃圾回收器有哪些&#xff1f; 2.3 Serial收集器 2.4 Parallel Scavenge收集器 2.5 ParNew收集器 2.6 CMS收集器 1.垃圾…

波特图方法

在电路设计中&#xff0c;波特图为最常用的稳定性余量判断方法&#xff0c;波特图的根源是如何来的&#xff0c;却鲜有人知。 本章节串联了奈奎斯特和波特图的渊源&#xff0c;给出了其对应关系和波特图相应的稳定性余量。 理论贯通&#xff0c;不在于精确绘…

【Java】2、集合框架 JCF

目录 CollectionListArrayList扩容机制System.arraycopy() 和 Arrays.copyOf()方法 LinkedList Set MapHashMap *重点&#xff1a; 底层机制&#xff08;源码&#xff09;应用场景 好处&#xff1a; 数组&#xff08;长度不可改&#xff0c;同一类型&#xff0c;增删不便&#…

P5461 赦免战俘

P5461 赦免战俘 #include <iostream> using namespace std; #include <algorithm> #include <vector> #include <cmath> void pardon(auto & matrix,int x,int y,int size){if(size 1) return;int half size / 2;for(int i x;i < x half;i …

GoTrackIt应用指南:共享单车时空轨迹可视化

GoTrackIt平台集成了对 Kepler.gl 可视化工具的部分功能进行了封装&#xff0c;通过引入 KeplerVis 类&#xff0c;显著简化了地理空间数据分析与展示的过程。利用这一类&#xff0c;开发者和数据分析师能够在网页端快速实现复杂地理数据的动态可视化&#xff0c;而无需深入掌握…

LeetCode 力扣 热题 100道(十五)搜索插入位置(C++)

给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 代码如下所示&#xff1a; class Solution { public:int searchIns…