循序渐进,搞懂什么是回溯算法

循序渐进,搞懂什么是回溯算法

回溯算法简介

回溯算法(backtracking algorithm)实际上是一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。满足回溯条件的某个状态的点称为“回溯点”。

回溯算法通常用于在候选解的所有可能的情况中搜索并找出满足条件的解。它的基本思路是从问题的某一种状态开始搜索,在搜索过程中不断尝试每一种可能的选择,直到找到满足条件的解或者所有可能的选择都已尝试完毕。如果当前的选择不能得到满足条件的解,就回溯到上一个状态并尝试其他选择。

回溯算法的关键在于定义问题的状态以及如何进行选择和回溯。首先需要为问题定义一个解空间(solution space),这个空间必须至少包含问题的一个解(可能是最优的)。

回溯法从开始结点(根结点)出发,以深度优先的方式搜索整个解空间,这个开始结点就成为一个“回溯点”,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点,这个新结点就成为一个新的“回溯点”,并成为当前扩展结点。如果搜索深度达到最大,扩展结点处不能再向纵深方向移动,应回溯至最近的一个“回溯点”,并使这个“回溯点”成为当前的扩展结点。回溯法以这种工作方式递归地在解空间中搜索,直至找到所求的解或解空间都已尝试完毕。

回溯算法按照深度优先的顺序,穷举所有的可能性,但是回溯算法比暴力穷举法更高明的地方就是回溯算法可以随时判断当前状态是否满足问题的解。一旦不满足,就退回到上一个状态,省去了继续往下搜索的时间,相当于对穷举进行了“剪枝”。

回溯算法的一般求解步骤

回溯算法通常使用递归来实现,每一层表示问题的一个状态,在递归的过程中,不断地重复做“选择”和“回溯”这两种操作。

在问题的初始状态(根结点),会先做出第一次“选择”,如果能选择的选项有N个,这N个选项组成的集合称为选择列表。做选择的过程相当于在一棵N叉树上做搜索,一般情况下,最终会生成一棵深度为N+1的N叉树。

在每做一次选择后,都会判断当前的状态是否满足问题的条件,如果满足,就会对当前状态做相应的处理,如更新选择列表、记录当前状态等,然后递归。如果不满足条件,就撤销做选择时的处理,往回“回溯”。

所以,回溯算法解决问题的一般步骤为:

  1. 定义问题的解空间,确定问题的解是什么形式的。

  2. 确定状态搜索树的结构,使得能用回溯法搜索整个解空间。

  3. 以深度优先的方式搜索解空间,并且在搜索过程中进行剪枝,避免无效搜索。搜索解空间就是全遍历,但是在遍历时可以及时判断当前是否满足问题的条件,不满足就跳过,进行剪枝。

回溯算法实现举例

回溯算法即涉及递归,又涉及多叉树的深度优先遍历,光看前面的概念,多半还是似懂非懂,还是要用具体的案例来与理论进行对照学习,才能加深理解。而回溯算法最经典的一个案例就是八皇后问题,下面就以八皇后问题的求解为例,来看一下回溯算法如何实现。

八皇后问题是指将八个国际象棋中的皇后放在 8*8 的棋盘上,并且使皇后彼此之间不能相互攻击。皇后可以攻击同一行、同一列、对角线方向。要满足8个皇后不互相攻击,那它们都不能在同一行、同一列以及同一条对角线上。

基于以上规则,开始分析如何用回溯算法进行求解。首先,皇后不能存在于同一行,也就是每一行只能有一个皇后,所以求解步骤就是依次在每一行放置一个皇后,枚举皇后所在的列,看是否满足不互相攻击的条件。求解时用一个列表来记录已放置皇后的位置,列表的下标刚好可以表示皇后所在的行,而列表中的每个值表示皇后所在的列,求出八个皇后所在列的组合即可。

根据这个解题思路,得到的每个解是长度为 8 的列表,一开始,可以初始化一个长度为 8 的列表来保存解,这就定好了问题的解空间(对应步骤1)。如果找到多个解,可以把所有解一起保存下来。

依次在每一行枚举可能的解,就是在解空间里面进行深度优先搜索(对应步骤2)。搜索用递归来实现,在递归代码中,需要做以下事情,如果放置了一个皇后,则要更新存在冲突的信息,然后递归,继续在下一行进行搜索。如果在当前这一行中找不到满足条件的位置,则要回溯到上一行,撤销上一行的选择和冲突信息,重新选择上一行。如果 8 个皇后全部放置完毕,则找到了一个可行解。

在每一次选择之后,立即判断当前位置是否满足条件,如果在某一行找不到满足条件的位置,就立即回溯,这个过程就会对搜索进行“剪枝”,降低问题的时间复杂度(对应步骤3)。

根据题目规则,不满足条件的情况(相互攻击)可以分成三种:

1.当前列已经放置了皇后。在代码中用一个集合 columns 记录已经有皇后的列,每放置或撤回一个皇后,都更新 columns 。

2.从左上到右下的斜线上已经放置了皇后。经过分析,同一条斜线上的行列数之差相等,并且不同斜线的行列数之差不相等。见下图中绿色的斜线,如(0, 0),(1,1),(2, 2)…这条斜线的行列之差都是0。(4, 0),(5, 1),(6, 2),(7, 3)这条斜线的行列之差都是4。在代码中用一个集合 diagonal_left 记录行列数之差,每放置或撤回一个皇后,都更新 diagonal_left 。

3.从右上到左下的斜线上已经放置了皇后。经过分析,同一条斜线上的行列数之和相等,并且不同斜线的行列数之和不相等,见下图黄绿色斜线。同理维护一个集合 diagonal_right 记录行列数之和,每放置或撤回一个皇后,都更新 diagonal_right 。
在这里插入图片描述

分析完成后,就可以开始实现代码了,下面的代码中写了详细注释,可以结合分析一起对照着看。

def solve8queens(n=8):solutions = []  # 保存所有解columns = set()  # 存在冲突的列diagonal_left = set()  # 存在冲突的对角线(方向左上到右下)diagonal_right = set()  # 存在冲突的对角线(方向右上到左下)result = [-1] * n  # 可行解的列表,初始化成长度为n,初始值全设为-1def backtrack(row):  # 回溯函数,传入行的下标rowif row == n:  # 如果row已经与n相等,说明已经找到了一个可行解# python的列表是可变对象,保存当前找到的解时,先转换成元组solutions.append(tuple(result))  returnelse:for i in range(n):# 遍历每一列,判断是否存在位置冲突,存在冲突则跳过,“剪枝”if i in columns or row-i in diagonal_left or row+i in diagonal_right:continue# 保存满足条件的值,并更新存在冲突的信息result[row] = icolumns.add(i)diagonal_left.add(row - i)diagonal_right.add(row + i)# 递归调用回溯函数backtrack(row + 1)# 回溯时删除对应的冲突信息columns.remove(i)diagonal_left.remove(row - i)diagonal_right.remove(row + i)backtrack(0)  # 选择一个初始状态,触发回溯函数return solutionsprint("可行解个数:", len(solve8queens()))
print(solve8queens()[0])

Output:

可行解个数: 92
(0, 4, 7, 5, 2, 6, 1, 3)

根据代码运行结果,可以看到八皇后问题一共有92种可行解,如果靠人工暴力求解,根本不可能。回溯算法是类似枚举的搜索算法,时间复杂度为 O(N!),问题规模每增加1,时间复杂度都会明显变大,即使回溯算法中有“剪枝”的效果,也仅仅是稍微缓解。

回溯算法的实现代码中,在函数的内部还定义了一个递归的函数,而且是在for循环下进行递归,虽然代码不多,但也不易理解,所以要结合理论和解题步骤进行理解。

本文的代码还可以扩展到求解 N 皇后问题,即求将N个皇后放置在 N*N 的棋盘上,不互相攻击,代码可以直接使用。

要加深对回溯算法的理解,可以多看一些相关的文章,看不同的人从不同的角度分析可能会加深理解,还可以多看代码实现方式和多看案例。


相关阅读:

循序渐进,搞懂什么是动态规划

📢欢迎 点赞👍 收藏⭐ 评论📝 关注 如有错误敬请指正!

☟ 学Python,点击下方名片关注我。☟

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

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

相关文章

图神经网络实战——基于DeepWalk创建节点表示

图神经网络实战——基于DeepWalk创建节点表示 0. 前言1. Word2Vec1.1 CBOW 与 skip-gram1.2 构建 skip-gram 模型1.3 skip-gram 模型1.4 实现 Word2Vec 模型 2. DeepWalk 和随机行走3. 实现 DeepWalk小结系列链接 0. 前言 DeepWalk 是机器学习 (machine learning, ML) 技术在图…

ABAP - SALV教程05 添加页眉和页脚

先看看效果叭CL_SALV_TABLE提供了SET_TOP_OF_LIST方法设置页眉显示和SET_TOP_OF_LIST_PRINT方法设置页眉打印来实现添加页眉的目的。CL_SALV_TABLE提供了SET_END_OF_LIST方法设置页脚显示和SET_END_OF_LIST_PRINT方法设置页脚打印来实现添加页脚的目的。这个四个方法的传入参数…

【go从入门到精通】go包,内置类型和初始化顺序

大家好,这是我给大家准备的新的一期专栏,专门讲golang,从入门到精通各种框架和中间件,工具类库,希望对go有兴趣的同学可以订阅此专栏。 go基础 。 Go文件名: 所有的go源码都是以 ".go" 结尾&…

[Java 探索者之路] 一个大厂都在用的分布式任务调度平台

分布式任务调度平台是一种能够在分布式计算环境中调度和管理任务的系统,在此环境下,各个任务可以在独立的节点上运行。它有助于提升资源利用率,增强系统扩展性以及提高系统对错误的容忍度。 文章目录 1. 分布式任务调度平台1. 基本概念1.1 任…

CVPR2023 | 提升图像去噪网络的泛化性,港科大上海AILab提出 MaskedDenoising,已开源!

作者 | 顾津锦 首发 | AIWalker 链接 | https://mp.weixin.qq.com/s/o4D4mNM3jL6sYuhUC6VgoQ 当前深度去噪网络存在泛化能力差的情况,例如,当训练集噪声类型和测试集噪声类型不一致时,模型的性能会大打折扣。作者认为其原因在于网络倾向于过度…

数据结构与算法学习【算法思想之二分法基础】

文章目录 数据结构与算法学习【算法思想之二分查找基础】本文学习目标或巩固的知识点 最基础的二分查找🟢通过题目可知题解结果验证 数据结构与算法学习【算法思想之二分查找基础】 本文学习目标或巩固的知识点 学习二分法类题目 巩固基础的二分法 提前说明&#…

git根据文件改动将文件自动添加到缓冲区

你需要修改以下脚本中的 use_cca: false 部分 #!/bin/bash# 获取所有已修改但未暂存的文件 files$(git diff --name-only)for file in $files; do# 检查文件中是否存在"use_cca: false"if grep -q "use_cca: false" "$file"; thenecho "Ad…

matplotlib从起点出发(14)_Tutorial_imshow_origin_extent

0 总述 imshow()允许你将图像(将进行颜色映射——基于norm和cmap——的2D数组或将按原样使用的3D RGB(A)的数组)渲染到数据空间中的矩形区域。最终渲染中图像的方向由原点和范围关键字参数(以及生成的AxesImage实例上的属性)和Ax…

复合机器人是一种集成了移动机器人

复合机器人是一种集成了移动机器人、协作机器人和机器视觉等多项功能的新型机器人。它的开发目的是为了解决工厂物流中最后一米的问题,提供智能搬运解决方案。复合机器人不仅集成了自主移动机器人(AMR)、机械臂等工作单元,还使用了…

Springboot+vue的高校教师教研信息填报系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频: Springbootvue的高校教师教研信息填报系统(有报告)。Javaee项目,springboot vue前后端分离项目。 项目介绍: 采用M(model)V(view)C(controller&am…

TypeScript08:在TS中使用模块化

前言:tsconfig.json中的配置 一、前端领域中的模块化标准 前端领域中的模块化标准有: ES6、commonjs、amd、umd、system、esnext 二、 TS中如何书写模块化语句 TS 中,导入和导出模块,统一使用 ES6 的模块化标准。 myModule.ts &a…

k8s-kubeapps图形化管理 21

结合harbor仓库 由于kubeapps不读取hosts解析,因此需要添加本地仓库域名解析(dns解析) 更改context为全局模式 添加repo仓库 复制ca证书 添加成功 图形化部署 更新部署应用版本 再次进行部署 上传nginx 每隔十分钟会自动进行刷新 在本地仓库…