回溯法基本思想-01背包、N皇后回溯法图解

基本思想:

​ 回溯法是一种系统地搜索问题解空间的算法,常用于解决组合优化和约束满足问题。其核心思想是利用深度优先搜索逐步构建可能的解,同时在搜索过程中进行剪枝操作,以排除那些无法满足问题约束或不能产生最优解的分支,从而减少不必要的计算,提高搜索效率。

深度优先搜索(DFS)简介:

​ 对于二叉树来说,先序、中序、后序遍历都是深度优先遍历。

​ 深度优先就是一条路径走到底后,再返回上一步,搜索第二条路径。

在这里插入图片描述

回溯法的一般步骤

1.定义问题并构造状态空间树

​ 明确要解决的问题,确定解空间的结构(通常是一个树或图),确定每一步决策的选择范围。

​ 将问题的解空间表示为一棵树,树的每个节点表示一个状态,根节点表示初始状态,叶节点表示最终状态或解。

2.编写递归函数

​ 编写一个递归函数来遍历状态空间树,函数通常包括以下部分:

  • 递归边界:定义何时到达叶节点,即找到一个解或无法继续深入。
  • 选择和判断(剪枝):在当前状态下,尝试每一种可能的选择,并判断是否满足约束条件。
  • 递归调用:如果满足条件,则进行递归调用,进入下一个状态。
  • 回溯:如果不满足条件,或递归调用返回后,需要撤销当前选择,回溯到上一步继续尝试其他选择。

3.输出结果

举例:01背包

问题描述:

​ 有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?

背包体积:10

物品编号体积(vol)价值(val)
189
232
344
433

​ 前面在动态规划中,说了一下蛮力法的如何解决该问题,实际上回溯法与蛮力法差不多,只是搜索方式不同,并添加剪枝(提前结束当前遍历)和回溯(记忆之前的状态)。

1、定义问题并构造状态空间树

​ 前面使用蛮力法解决时,对于问题的每一种状态通过数值的二进制表示,用数字二进制中为1的位置表示该位置的物品是否放入,比如:

​ 1(0000 0001)表示第1个物品放入,其他都不放入;

​ 2(0000 0010)表示第2个物品放入,其他都不放入;

​ 3(0000 0011)表示第1和第2哥物品放入,其他都不放入。

​ 使用回溯法解决问题,需要将问题所有的情况构造成一颗树或图,通过深度优先搜索遍历获取最优解。

​ 每个物品只有两种状态,放入背包或不放入背包,可以将各种物品是否放入构造成一颗二叉树,如下图:

在这里插入图片描述

2、选择和判断(剪枝)

​ 边界:当当前物品为最后一个物品时,则到达叶子节点,递归结束。

​ 选择:每一步递归,针对当前物品,都存在放与不放两个选择。

​ 剪枝:如果放入当前物品,通过判断放入后当前物品的总体积是否超过总体积,如果超过就进行剪枝。

​ 回溯:当当前状态的所有情况考虑完之后,进行回溯。

剪枝:

​ 在第一个物品放入,第二个物品尝试放入时,发现放入第二个物品后,体积变为:8+3=11> 背包体积=10,所以,在第一、第二个物品放入,无论剩下的物品怎么放,都会超出背包体积,此时进行剪枝,减少了大量的计算。

​ 对比蛮力法:对于1100、1101、1110、1111这四种情况,其都会去计算一次,发现超出背包容量再结束,而回溯则在计算1100时,发现超出背包容量就直接剪枝,后面的其他几种情况都不再计算。

回溯:

​ 在计算了0111这种情况后,进行回溯,到达011这一步,然后计算0110,直接使用了011这一步的状态(当前体积7,当前价值6),在这个基础上计算0110。

​ 对比蛮力法:在计算了0111后,再次计算0110这种情况时,还需要计算011的体积和价值,然而这一步在计算0111时,011状态的体积和价值就已经计算过了,蛮力法进行了重复计算,而回溯法则保存了之前的状态,减少了重复计算。

3、输出结果

​ 在递归过程中,更新最大价值,最终输出,

4、代码实现

​ 不需要真的去构建这颗二叉树,通过递归模拟二叉树即可。

public class ZeroOneBackpackBacktrack {private static int maxValue = 0;public static void main(String[] args) {int maxVolume = 10;Item[] items = new Item[]{new Item(8, 9),new Item(3, 2),new Item(4, 4),new Item(3, 3)};execute(items, maxVolume);System.out.println(maxValue);}public static void execute(Item[] items, int maxVolume) {zeroOneBackpackBacktrack(items, maxVolume, 0, 0, 0);}public static void zeroOneBackpackBacktrack(Item[] items, int maxVolume, int index, int currentVolume, int currentValue) {// 当前体积已经超出背包最大体积if (currentVolume > maxVolume) return;// 更新最大价值maxValue = Math.max(currentValue, maxValue);// 未到达最后一个物品if (index < items.length) {// 放入当前物品zeroOneBackpackBacktrack(items, maxVolume, index + 1, currentVolume + items[index].getVolume(), currentValue + items[index].getValue());// 不放入当前物品zeroOneBackpackBacktrack(items, maxVolume, index + 1, currentVolume, currentValue);}}
}class Item {int volume;int value;public Item(int volume, int value) {this.volume = volume;this.value = value;}public int getValue() {return value;}public int getVolume() {return volume;}
}

回溯法经典案例-N皇后图解

问题描述:

​ 根据国际象棋的规则,皇后可以攻击与同处一行、一列或一条斜线上的棋子。给定 𝑛 个皇后和一个 𝑛×𝑛 大小的棋盘,寻找使得所有皇后之间无法相互攻击的摆放方案。

例如:

​ 对于n = 4 的情况,有下面两种可能的摆放方法。

在这里插入图片描述

在这里插入图片描述

蛮力法:

​ 蛮力法只需要将所有的情况都遍历到即可,对于每一行,尝试在每一列放置一个皇后,生成所有可能的放置方案。

​ 每一行有n列,一共n行,遍历每种情况就需要 n ∗ n ∗ n . . . ∗ n = n n n*n*n...*n = n^n nnn...n=nn次,再加上每一次都要判断所有的皇后位置是否正确,又需要遍历。所以复杂度特别高。

    public static void nQueens(int[][] data, int row) {int n = data.length;// 已经到达最后一行,n个皇后已经放置完毕if (row == n) {// 校验皇后拜访位置是否合理for (int row1 = 0; row1 < n; row1++) {for (int col = 0; col < n; col++) {if (data[row1][col] == 1) {// 判断每一行中皇后位置是否合理if (!canPlace(data, row1, col)) {// 不合理直接返回return;} else {// 到达最后一行输出结果if (row1 == n - 1) {System.out.println("第" + ++num + "组解:");for (int[] d : data) {System.out.println(Arrays.toString(d));}}}}}}}else{// 未到达最后一行,遍历for (int col = 0; col < n; col++) {data[row][col] = 1;nQueens(data, row + 1);data[row][col] = 0;}}}public static boolean canPlace(int[][] data, int row, int col) {// 检查同一列是否有皇后for (int i = 0; i < row; i++) {if (data[i][col] == 1) return false;}// 检查 \ 对角线是否存在皇后for (int i = 1; row - i >= 0 && col - i >= 0; i++) {if (data[row - i][col - i] == 1) return false;}// 检查 / 对角线是否存在皇后for (int i = 1; row - i >= 0 && col + i < data.length; i++) {if (data[row - i][col + i] == 1) return false;}return true;}
回溯法:
1、定义问题并构造状态空间树

​ 根据棋盘大小 n ∗ n n*n nn n n n个皇后,以及皇后可以攻击与其处于同一行上的其他皇后可知,每一行仅允许放置一个皇后。可以按照逐行放置的思路:从第一行开始,在每行放置一个皇后,直至最后一行结束。

​ 那么每一行实际上就有n个选择,每个位置是否放入皇后,放入皇后之后,就可以进入下一行放置下一个皇后。

​ 采用一个n叉树就可以表示,这里使用一个二维数组表示:

    public static void dfs(int[][] data, int row) {for (int col = 0; col < data.length; col++) {// 放入皇后data[row][col] = 1;// 放入下一个皇后dfs(data, row + 1);// 取出当前皇后(回溯):每一行只能放置一个,取出当前列皇后,下一列放入data[row][col] = 0;}}}
2、选择和判断(剪枝)

​ 在某一行的某一列放置皇后时,可能会出现如果该位置放置皇后,就会与前几行放置的皇后冲突,那么就可以提前剪枝,直接不用放置后续的皇后了,直接去尝试再下一列放置。

	public static void dfs(int[][] data, int row) {for (int col = 0; col < data.length; col++) {// 剪枝:判断该位置是否可以放入,不可放入则直接终止if (canPlace(data, row, col)) {// 放入皇后data[row][col] = 1;// 放入下一个皇后dfs(data, row + 1);// 取出当前皇后(回溯):每一行只能放置一个,取出当前列皇后,下一列放入data[row][col] = 0;}}}public static boolean canPlace(int[][] data, int row, int col) {// 检查同一列是否有皇后for (int i = 0; i < row; i++) {if (data[i][col] == 1) return false;}// 检查 \ 对角线是否存在皇后for (int i = 1; row - i >= 0 && col - i >= 0; i++) {if (data[row - i][col - i] == 1) return false;}// 检查 / 对角线是否存在皇后for (int i = 1; row - i >= 0 && col + i < data.length; i++) {if (data[row - i][col + i] == 1) return false;}return true;}
3、输出结果

​ 当放置到最后一行时,此时所有皇后均放入,可以输出结果

	public static void dfs(int[][] data, int row) {// 最后一个皇后已经放入if (row == data.length) {printResult(data, ++resultNum);return;}for (int col = 0; col < data.length; col++) {// 剪枝:判断该位置是否可以放入,不可放入则直接终止if (canPlace(data, row, col)) {// 放入皇后data[row][col] = 1;// 放入下一个皇后dfs(data, row + 1);// 取出当前皇后(回溯):每一行只能放置一个,取出当前列皇后,下一列放入data[row][col] = 0;}}}public static void printResult(int[][] data, int num) {System.out.println("第" + num + "组解:");for (int i = 0; i < data.length; i++) {System.out.println(Arrays.toString(data[i]));}}
图解说明:

​ 因为八皇后如果画图篇幅过大,这里用四皇后讲解:

​ 其中白色表示尚未遍历,绿色表示放入皇后,红色表示被剪枝(此路不通)

1、给第一行第一列放入皇后,进入第二行在第二行第一列放入皇后,剪枝

在这里插入图片描述

2、放入第二行第二列,剪枝

在这里插入图片描述

3、放入第二行第三列,皇后可以放置,再尝试放入第三行

在这里插入图片描述

4、第二行第三列放入皇后的所有情况均被剪枝,放入第二行第四列

​ 放入第三行第一列时剪枝,放入第三行第二列

在这里插入图片描述

5、第三行确定后,放入第四行(最后一个皇后)

​ 可见,在第四行第三列放入皇后时,符合要求,直接输出结果,其他几种情况均不符合情况

在这里插入图片描述

6、回溯,计算第三行放入第三列的情况

7、重复上述步骤

具体流程下图:

​ 可以看到,这其实就是深度优先搜索,添加了剪枝

在这里插入图片描述

优化:

​ ​ 上面在判断某个位置是否可以放置皇后时,使用的计算过方式需要遍历,会导致每次计算时复杂度较高。

public static boolean canPlace(int[][] data, int row, int col) {// 检查同一列是否有皇后for (int i = 0; i < row; i++) {if (data[i][col] == 1) return false;}// 检查 \ 对角线是否存在皇后for (int i = 1; row - i >= 0 && col - i >= 0; i++) {if (data[row - i][col - i] == 1) return false;}// 检查 / 对角线是否存在皇后for (int i = 1; row - i >= 0 && col + i < data.length; i++) {if (data[row - i][col + i] == 1) return false;}return true;}

可以改为如下方式:

​ 可以利用一个长度为 𝑛 的布尔型数组 existCol 记录每一列是否有皇后。在每次决定放置前,我们通过 cols 将已有皇后的列进行剪枝,并在回溯中动态更新 cols 的状态。

那么,如何处理对角线约束呢?

​ 设棋盘中某个格子的行列索引为 (𝑟𝑜𝑤,𝑐𝑜𝑙) ,选定矩阵中的某条主对角线,我们发现该对角线上所有格子的行索引减列索引都相等,即对角线上所有格子的 𝑟𝑜𝑤−𝑐𝑜𝑙 为恒定值

​ 也就是说,如果两个格子满足 𝑟𝑜𝑤1−𝑐𝑜𝑙1=𝑟𝑜𝑤2−𝑐𝑜𝑙2 ,则它们一定处在同一条主对角线上。利用该规律,我们可以借助如图所示的数组 existLeftDiagonal 记录每条主对角线上是否有皇后。

​ 容易看出,记录某一列是否有皇后的数组existLeftDiagonal长度为2N-1

在这里插入图片描述

​ 同理,次对角线上的所有格子的 𝑟𝑜𝑤+𝑐𝑜𝑙 是恒定值。我们同样也可以借助数组 existRightDiagonal 来处理次对角线约束。

public static boolean canPlace(int row, int col, int n, boolean[] existCol, boolean[] existLeftDiagonal, boolean[] existRightDiagonal) {return !(existCol[col] || existLeftDiagonal[row - col + n - 1] || existRightDiagonal[row + col]);}

完整代码:

package backtracking;import java.util.Arrays;public class EightQueens2 {private static int num = 0;public static void main(String[] args) {execute(9);}public static void execute(int n) {// 皇后存在情况表int[][] data = new int[n][n];// 皇后存在列情况boolean[] existCol = new boolean[n];// 皇后存在对角线 \ 情况 (可以发现处于同一对角线的元素,行 - 列是同一个值,所以可以使用这个性质来存储对角线信息)boolean[] existLeftDiagonal = new boolean[2 * n - 1];// 皇后存在对角线 / 情况(可以发现处于同一对角线的元素,行+ 列是同一个值,所以可以使用这个性质来存储对角线信息)boolean[] existRightDiagonal = new boolean[2 * n - 1];dfs(data, n, 0, existCol, existLeftDiagonal, existRightDiagonal);}private static void dfs(int[][] data, int n, int row, boolean[] existCol, boolean[] existLeftDiagonal, boolean[] existRightDiagonal) {if (row == n) {// 所有皇后已放置完毕,输出printResult(data, ++num);}for (int col = 0; col < n; col++) {// 剪枝:判断该位置是否可以放入,不可放入则直接终止if (canPlace(row, col, n, existCol, existLeftDiagonal, existRightDiagonal)) {// 放入皇后placeQueen(row, col, n, data, existCol, existLeftDiagonal, existRightDiagonal);// 放入下一个皇后dfs(data, n, row + 1, existCol, existLeftDiagonal, existRightDiagonal);// 取出皇后cancelPlaceQueen(row, col, n, data, existCol, existLeftDiagonal, existRightDiagonal);}}}public static boolean canPlace(int row, int col, int n, boolean[] existCol, boolean[] existLeftDiagonal, boolean[] existRightDiagonal) {return !(existCol[col] || existLeftDiagonal[row - col + n - 1] || existRightDiagonal[row + col]);}public static void placeQueen(int row, int col, int n, int[][] data, boolean[] existCol, boolean[] existLeftDiagonal, boolean[] existRightDiagonal) {data[row][col] = 1;existCol[col] = existLeftDiagonal[row - col + n - 1] = existRightDiagonal[row + col] = true;}public static void cancelPlaceQueen(int row, int col, int n, int[][] data, boolean[] existCol, boolean[] existLeftDiagonal, boolean[] existRightDiagonal) {data[row][col] = 0;existCol[col] = existLeftDiagonal[row - col + n - 1] = existRightDiagonal[row + col] = false;}public static void printResult(int[][] data, int num) {System.out.println("第" + num + "组解:");for (int[] d : data) {System.out.println(Arrays.toString(d));}}
}

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

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

相关文章

js修改scss变量

style.scss $color : var(--color,#ccc); // 默认值 #ccc .color{background: $color; } 定义了一个scss变量&#xff08;$color&#xff09;&#xff0c;用普通的css变量&#xff08;--color&#xff09;给他赋值&#xff0c;这里需要一个默认值&#xff0c;此时css变量(--co…

数据结构复习指南

数据结构复习指南 本文中列举了数据结构期末考试可能存在的考点 绪论 数据的基本单位 数据元素是数据的基本单位 数据项 数据项是组成数据的、有独立含义的、不可分割的最小单位。 数据对象 数据对象是性质相同的数据元素的集合&#xff0c;是数据的一个子集。 数据结…

如何借助ai(文心一言)获取tushare的数据

1. 准备工作 确保已安装python &#xff0c;安装Tushare库 和文心一言的地址&#xff08;文心一言&#xff09;&#xff1a; 注册Tushare账号并获取Token&#xff1a;在Tushare官方网站注册账号&#xff0c;并获取个人Token。如下 tushare地址&#xff1a;&#xff08;点击即…

【高级篇】InnoDB引擎深入:核心机制与实战优化(十五)

引言 在探索了MySQL集群与分布式技术之后,我们进入了数据库引擎的核心地带——InnoDB。作为MySQL的默认存储引擎,InnoDB凭借其对事务的支持、行级锁定、高效的恢复机制以及复杂的内存管理,成为众多应用场景的首选。本章,我们将深入InnoDB的内部机制,透彻理解锁管理、事务…

NeRF从入门到放弃6:两种OpenCV去畸变模型

针孔相机和鱼眼相机的去畸变模型是不一样的。 针孔相机的畸变参数有12个&#xff0c;k1~k6是径向畸变参数&#xff0c;p1 p2是切向畸变&#xff0c;s1s4&#xff1b;而鱼眼相机是等距模型&#xff0c;畸变参数只有4个k1k4。 针孔相机 畸变分为径向畸变和切向畸变。 把相机平…

【高考志愿】集成电路科学与工程

目录 一、专业概述 二、课程设置 三、就业前景 四、适合人群 五、院校推荐 六、集成电路科学与工程专业排名 一、专业概述 集成电路科学与工程&#xff0c;这一新兴且引人注目的交叉学科&#xff0c;正在逐渐崭露头角。它集合了电子工程、计算机科学、材料科学等多个领域的…

【Cpolar】如何实现外部网络对内部网络服务的访问

希望文章能给到你启发和灵感&#xff5e; 如果觉得文章对你有帮助的话&#xff0c;点赞 关注 收藏 支持一下博主吧&#xff5e; 阅读指南 开篇说明一、基础环境说明1.1 硬件环境1.2 软件环境 二、什么是Cpolar&#xff1f;三、如何安装Cpolar?3.1 Mac系统安装 四、最后 开篇说…

ChatGPT之母:AI自动化将取代人类,创意性工作或将消失

目录 01 AI取代创意性工作的担忧 1.1 CTO说了啥 02 AI已开始大范围取代人类 01 AI取代创意性工作的担忧 几天前的采访中&#xff0c;OpenAI的CTO直言&#xff0c;AI可能会扼杀一些本来不应该存在的创意性工作。 近来一篇报道更是印证了这一观点。国外科技媒体的老板Miller用…

! Warning: `flutter` on your path resolves to

目录 项目场景&#xff1a; 问题描述 原因分析&#xff1a; 解决方案&#xff1a; 1. 检查并更新.bash_profile或.zshrc文件 2.添加Flutter路径到环境变量 3. 加载配置文件 4.验证Flutter路径 5.重新启动终端 项目场景&#xff1a; 今天重新安装了AndroidStudio,并配置…

北京市大兴区餐饮行业协会成立暨职业技能竞赛总结大会成功举办

2024年6月27日下午&#xff0c;北京市大兴区营商服务中心B1层报告厅迎来了北京市大兴区餐饮行业协会成立仪式暨2024年北京市大兴区餐饮行业职工职业技能竞赛总结大会。此次活动不仅标志着大兴区餐饮行业协会的正式成立&#xff0c;也对在2024年大兴区餐饮行业职工职业技能竞赛中…

创新实训(十三) 项目开发——实现用户终止对话功能

思路分析&#xff1a; 如何实现用户终止AI正在进行的回答&#xff1f; 分析实现思路如下&#xff1a; 首先是在用户点击发送后&#xff0c;切换终止对话&#xff0c;点击后大模型终止对话&#xff0c;停止sse&#xff0c;不再接收后端的消息。同时因为对话记录存入数据库是后…

2小时动手学习扩散模型(pytorch版)【入门版】【代码讲解】

2小时动手学习扩散模型&#xff08;pytorch版&#xff09; 课程地址 2小时动手学习扩散模型&#xff08;pytorch版&#xff09; 课程目标 给零基础同学快速了解扩散模型的核心模块&#xff0c;有个整体框架的理解。知道扩散模型的改进和设计的核心模块。 课程特色&#xf…

基于VMware的linux操作系统安装(附安装包)

目录 一、linux操作系统下载链接 二、开始导入镜像源 注&#xff1a;若是还没安装VMware请转到高效实现虚拟机&#xff08;VMware&#xff09;安装教程&#xff08;附安装包&#xff09;-CSDN博客 一、linux操作系统下载链接 1.官网链接下载 ubuntu&#xff1a;ubuntu官网…

港湾周评|胖东来为什么是胖东来?蜜雪冰城为什么差之千里?

《港湾商业观察》李镭 似乎每一次胖东来的热搜&#xff0c;都堪称为教科书般化不利为有利&#xff0c;变坏事为好事。 6月27日凌晨&#xff0c;“胖东来商贸集团”官方公众号发布《关于新乡胖东来餐饮商户“擀面皮加工场所卫生环境差”的调查报告》&#xff0c;对于帮助其发现…

【C++ | 类型转换】转换构造函数、类型转换运算符 详解及例子源码

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a; 本文未经允许…

git 代码回退 soft hard区别

一:只是本地修改提交到本地版本库仓库,代码如何回退 git hard 回退 会清除掉 你当前修改的所有文件代码内容 或添加的新文件 把当前文件恢复到没有修改前的状态 git soft 回退 不会清除掉 你当前修改的所有文件代码内容 或添加的新文件 把当前文件恢复到当时修改时的状…

Linux /proc目录总结

1、概念 在Linux系统中&#xff0c;/proc目录是一个特殊的文件系统&#xff0c;通常被称为"proc文件系统"或"procfs"。这个文件系统以文件系统的方式为内核与进程之间的通信提供了一个接口。/proc目录中的文件大多数都提供了关于系统状态的信息&#xff0…

Linux容器篇-Docker容器的使用

文章目录 前言一、Docker的安装主机环境准备关闭防火墙关闭selinux时间同步关闭 swap配置操作系统yum源配置国内Docker-ce镜像源注意 二、安装docker-ce三、配置镜像加速器阿里云镜像加速器生成 四、Docker的使用Docker 客户端获取镜像启动容器查看所有的容器&#xff1a;启动已…

【Python】已解决:ModuleNotFoundError: No module named ‘LAC‘

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;ModuleNotFoundError: No module named ‘LAC‘ 一、分析问题背景 在开发或运行Python程序时&#xff0c;可能会遇到各种各样的报错&#xff0c;其中“ModuleNo…

安装OpenHarmony编译库和工具集

一、搭建开发环境 1.1、Ubuntu搭建&#xff0c;参考 VMware完美安装Ubuntu20.04-CSDN博客文章浏览阅读286次&#xff0c;点赞5次&#xff0c;收藏3次。详细介绍了VMware下安装Ubuntu20.04https://blog.csdn.net/longyuzi/article/details/139935769 1.2、拉取OpenHarmony源码…