JAVA中的回溯算法解空间树,八皇后问题以及骑士游历问题超详解

1.回溯算法的概念

回溯算法顾名思义就是有回溯的算法

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

回溯算法的核心思想是:

  1. 问题分解:将原问题分解成若干个规模较小的子问题。
  2. 候选解的生成:从根节点开始,逐层生成候选解。
  3. 活节点:当前正在扩展的节点称为活节点。
  4. 死节点:当前扩展的节点如果已经确定不能得到问题的解,则该节点称为死节点。
  5. 剪枝:根据问题的限制条件,对已经确定的死节点进行剪枝。

2.回溯算法解空间树的问题

 典型案例:

集合求幂集的解空间树

意思就是一个集合s所有的子集的集合

问题:求集合S={1,2,3}的所有子集,依次选择每个元素的过程就是一棵树,如图所示

在树中,由从根节点到叶子结点的每条路径上的权值组成三元组(0,0,0)~(1,1,1)都是解,如(0,0,1)表示子集{B,C},共2^{3}=8个解;因此,该树被称为解空间树,高度为4。

为什么帮助理解,我们先看普通的递归方法是什么实现的

下面是迭代方法代码实现:

import java.util.ArrayList;
import java.util.List;public class PowerSet {public static void main(String[] args) {List<String> originalSet =new ArrayList<>();originalSet.add("A");originalSet.add("B");originalSet.add("C");//这里又嵌套了一个list是为了使每一个元素都变成list//适用于多种算法和数据处理任务List<List<String>> powerSet =getPowerSet(originalSet);for (List<String> subset:powerSet){System.out.println(subset);}}private static List<List<String>> getPowerSet(List<String> originalSet) {List<List<String>> powerSet =new ArrayList<>();if (originalSet==null||originalSet.isEmpty()){return powerSet;}//添加空集powerSet.add(new ArrayList<>());for (String element:originalSet){int size =powerSet.size();for (int i =0;i<size;i++){List<String> subset =new ArrayList<>(powerSet.get(i));subset.add(element);powerSet.add(subset);}}return powerSet;}

现在让我们来分析一下这个过程:

假设第一次迭代中,我们处理元素“A”:

  • 初始时,powerSet只包含一个空集[ ]。
  • 我们复制这个空集,然后添加“A”,得到["A"]。
  • 将["A"]添加到powerSet,现在powerSet是[ ],["A"]。

在二次迭代中处理“B”:

  • 现在powerSet有两个子集:[ ]和["A"]。
  • 我们首先复制空集,然后添加“B”,得到["B"]。
  • 然后复制[“A”],然后添加“B”,得到["A","B"]。
  • 然后将这两个新的子集添加到powerSet,现在powerSet有[ ],["A"],["B"],["A","B"]

然后三次迭代重复这个过程就可以了。

下面是回溯方法的代码:

public class PowerSetWithBacktracking {public static void main(String[] args) {List<String> originalSet = new ArrayList<>();originalSet.add("A");originalSet.add("B");originalSet.add("C");List<List<String>> powerSet = new ArrayList<>();backtrack(0, originalSet, new ArrayList<>(), powerSet);for (List<String> subset : powerSet) {System.out.println(subset);}}private static void backtrack(int start, List<String> originalSet, List<String> currentSubset, List<List<String>> powerSet) {//将当前子集的副本添加到幂集powerSet中,这里使用new ArrayList<>()来复制当前的子集。powerSet.add(new ArrayList<>(currentSubset));//遍历集合中的每个元素,从start开始for (int i =start;i<originalSet.size();i++){//做出选择:包含当前元素currentSubset.add(originalSet.get(i));//递归调用:移动到下一个元素backtrack(i+1,originalSet,currentSubset,powerSet);//撤销选择:回溯currentSubset.remove(currentSubset.size()-1);}
}
}

分析过程:

步骤startcurrentSubsetpowerSet
第一次0[ ][[ ]]
第二次0["a"][[ ]]
第三次1["a","b"][[ ]]
第四次2["a","b","c"][[ ]]
第五次3["a","b","c"][[" "],["a","b","c"],["a","b"],["a"]]
第六次2["a","b"][[" "],["a","b","c"],["a","b"],["a"]]
第七次1["a"][[" "],["a","b","c"],["a","b"],["a"]]
第八次2["a","c"][[" "],["a","b","c"],["a","b"],["a"]]
第九次2["a","c"][[" "],["a","b","c"],["a","b"],["a"],["a","c"]]
第十次1["a"][[" "],["a","b","c"],["a","b"],["a"],["a","c"]]
第十一次1[ ][[" "],["a","b","c"],["a","b"],["a"],["a","c"]]
第十二次1["b"][[" "],["a","b","c"],["a","b"],["a"],["a","c"],["b"]]

 接下来依次推导就行了。整体的思路就是不断递归下一个元素,然后从currentSubset中移除最后添加的元素,进行回溯,准备处理下一个元素。

3.八皇后问题

八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上吗,问有多少种摆法。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当n = 1或n ≥ 4时问题有解

public class EightQueens {private static final int n =8;private static int[] queens;//存储皇后的位置private static int count =0;//解的计数public static void main(String[] args){queens=new int[n];for (int i =0;i<n;i++){queens[i]=-1;//初始化都为-1,表示都没有放皇后}placeQueens(0);//从第0行开始放置皇后System.out.println("");}private static void placeQueens(int row) {if (row==n){count++;printBoard();return;}for (int col =0;col<n;col++){if (isSafe(row,col)){queens[row]=col;//在当前位置放置皇后placeQueens((row+1));//进入下一行queens[row]=-1;//回溯,撤销当前行的皇后放置的位置}}}private static boolean isSafe(int row, int col) {//检查列是否有冲突for (int i =0;i<row;i++ ){if (queens[i]==col){return false;}}//检查左上对角线是否有冲突for (int i =row-1,j=col+1;i>=0&&j>=0;i--,j--){if (queens[i]==j){return false;}}//检查右下对角线是否有冲突for (int i =row-1,j=col+1;i>=0&&j<n;i--,j++){if (queens[i]==j){return false;}}return true;}private static void printBoard() {System.out.println("第"+count+"种解法");for(int i =0;i<n;i++){for (int j=0;j<n;j++){if (queens[i]==j){System.out.println("Q");}else{System.out.println(". ");}}System.out.println();}System.out.println();}
}

过程分析:

回溯发生在两种情况下:

  1. 当前行是最后一行(row==n-1),并且已经尝试了所有可能的列,并且已经找到了一个解决方案,递归将终止,开始回溯
  2. 在递归过程中,当前行不是最后一行,但在下一行无法找到任何安全位置放置皇后。

 4.骑士游历问题

骑士游历问题是指,在国际象棋的棋盘(8行*8列)上,一个马要遍历棋盘,即走到棋盘上的每一格,并且每隔只到达一次。 设码在棋盘的某一位置(x,y)上,按照“走马日”的规则,下一步有8个方向走,如图所示。 若给定起始位置(x0,y0),使用站和队列探索出一条马遍历棋盘的路径。

 算法步骤:

  • 定义棋盘大小和骑士的可能移动的方式。
  • 创建一个二维数组来表示棋盘,初始值设为-1表示未访问。
  •  编写一个辅助函数来检查当前位置是否合法。
  • 编写一个递归函数来尝试所有可能的移动,使用回溯法来寻找解。
  • 在主函数中调用递归函数,并从指定的起始点开始。


public class KnightTour{private static final int N =8;private static final int[][] moves={{2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}};public static void main(String[] args) {int[][] board =new int[N][N];for (int i =0;i<N;i++){for (int j =0;j<N;j++){board[i][j]=-1;}}//起始点int x =0,y =0;board[x][y]=0;if (!solveKTUtil(board,x,y,1)){System.out.println("此解决方案不存在");}else{printSolution(board);}}private static boolean solveKTUtil(int[][] board, int x, int y, int moveCount) {if (moveCount==N*N){return true;}for (int i =0;i<8;i++){int nextX =x+moves[i][0];int nextY =y+moves[i][1];if (isSafe(board,nextX,nextY)){board[nextX][nextY]=moveCount;if (solveKTUtil(board,nextX,nextY,moveCount+1)){return true;}else{board[nextX][nextY]=-1;//回溯}}}return false;}private static boolean isSafe(int[][] board, int X, int Y) {return X>=0&&Y>=0&&X<N&&Y<N&&board[X][Y]==-1;}private static void printSolution(int[][] board){for (int i =0;i<N;i++){for(int j =0;j<N;j++){System.out.print(board[i][j]+" ");}System.out.println();}}
}

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

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

相关文章

[线性RNN系列] Mamba: S4史诗级升级

前言 iclr24终于可以在openreview上看预印本了 这篇&#xff08;可能是颠覆之作&#xff09;文风一眼c re组出品&#xff1b;效果实在太惊艳了&#xff0c;实验相当完善&#xff0c;忍不住写一篇解读分享分享。 TL;DR &#xff08;overview&#xff09; Structured State-Sp…

xshell公钥免密登录

设备&#xff1a;一台linux系统机器&#xff0c;一台windows系统机器 软件&#xff1a;xshell 要求&#xff1a;公钥免密登录 一、生成公钥、私钥 1、打开shell &#xff1b; 点击工具 &#xff1b; 新建用户生成密钥向导 2、生成密钥参数 密钥类型&#xff1a;RS…

element ui ts table重置排序

#日常# 今天带的实习生&#xff0c;在遇到开发过程中&#xff0c;遇到了element ui table 每次查询的时候都需要重置排序方式&#xff0c;而且多个排序是由前端排序。 <el-table :data"tableData" ref"restTable"> </<el-table> <script…

bi项目笔记

1.bi是什么 bi项目就是商业智能系统&#xff0c;也就是数据可视画、报表可视化系统&#xff0c;如下图的就是bi项目了 2.技术栈

Linux rsync文件同步工具

scp的不足 1. 性能问题 单线程传输 SCP只使用单线程进行传输&#xff0c;这意味着在传输大文件或大量小文件时&#xff0c;其传输速度和效率可能不如其他多线程工具。 无法压缩数据传输 SCP不支持内置的压缩机制&#xff0c;这在传输大文件时会导致带宽使用效率较低。 2.…

我花了5年时间训练自己这种能力,希望你也能成功

人生最重要的能力是日拱一卒&#xff0c;即每天做一点点对自己有利的事并持续足够长的时间。作者之前急于求成&#xff0c;减肥失败。同事通过每月改进一件小事成功减肥且知识储备丰富。作者受启发后&#xff0c;通过走楼梯、换代糖等小改变&#xff0c;用 4 年减了 40 斤&…

从头开始搭建一套Elasticsearch集群

前言 刚开始使用ES接触的就是rpm或者是云上提供的ES服务&#xff0c;基本上开箱即用。特别是云上的ES服务&#xff0c;开局就是集群版本&#xff0c;提供的是优化后的参数配置、开箱即匹配访问鉴权及常用插件&#xff0c;如无特殊需要基本上屏蔽了所有细节&#xff0c;直接可投…

深入了解 MySQL 的 EXPLAIN 命令

一、什么是 EXPLAIN 命令&#xff1f; EXPLAIN 命令用于显示 MySQL 如何执行某个 SQL 语句&#xff0c;尤其是 SELECT 语句。通过 EXPLAIN 命令&#xff0c;可以看到查询在实际执行前的执行计划&#xff0c;这对于优化查询性能至关重要。 二、EXPLAIN 的基本用法 要使用 EXP…

如何禁用键盘上的特定键或快捷方式?这里有详细步骤

要禁用特定的键盘键或快捷键吗&#xff1f;微软官方应用程序Microsoft PowerToys使这项任务变得非常简单。以下是使用Microsoft PowerToys中的键盘管理器禁用特定键或快捷方式的快速指南。 如果你还没有安装Microsoft PowerToys 如果你的设备上没有安装Microsoft PowerToys&a…

springboot上传图片

前端的name的值必须要和后端的MultipartFile 形参名一致 存储本地

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【匿名密钥证明(C/C++)】

匿名密钥证明(C/C) 在使用本功能时&#xff0c;需确保网络通畅。 在CMake脚本中链接相关动态库 target_link_libraries(entry PUBLIC libhuks_ndk.z.so)开发步骤 确定密钥别名keyAlias&#xff0c;密钥别名最大长度为64字节&#xff1b;初始化参数集&#xff1a;通过[OH_Huk…

css3 transform的旋转和位移制作太阳花

css3 transform 实例展示知识点rotate 旋转translate 位移transform: translate(300px,200px) rotate(90deg) 实例代码 实例展示 知识点 transform的两个属性 rotate 旋转 translate 位移 transform: translate(300px,200px) rotate(90deg) 实例代码 <!DOCTYPE html&g…

flask 定时任务(APScheduler)使用current_app app_context()上下文

前言: 描述&#xff1a;flask定时任务调用的方法中使用了current_app.logger.info()记录日志报错 报错代码 raise RuntimeError(unbound_message) from None RuntimeError: Working outside of application context.This typically means that you attempted to use functiona…

IDEA中Git常用操作及Git存储原理

Git简介与使用 Intro Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency. Git是一款分布式版本控制系统&#xff08;VSC&#xff09;&#xff0c;是团队合作开发…

算法学习笔记(8.3)-(0-1背包问题)

目录 最常见的0-1背包问题&#xff1a; 第一步&#xff1a;思考每轮的决策&#xff0c;定义状态&#xff0c;从而得到dp表 第二步&#xff1a;找出最优子结构&#xff0c;进而推导出状态转移方程 第三步&#xff1a;确定边界条件和状态转移顺序 方法一&#xff1a;暴力搜素…

MFC之对话框--线宽/线型/颜色

文章目录 线宽输入实现优化无法记录上一次线粗问题 线宽滑动实现实现选择线类型实现颜色选择总结 线宽输入实现 优化无法记录上一次线粗问题 线宽滑动实现 实现选择线类型 实现颜色选择 总结 1。创建新窗口&#xff08;dialog)会创建一个新的类&#xff0c;在类中实现窗口中的…

vue中父子传递属性值

1、父传子属性值 自定义图库组件 在add.vue中应用tuku组件并给默认值 效果 2、 子传父&#xff0c;逆向赋值 add.vue和第一问中一样 修改tuku组件&#xff0c;传值给add.vue 3、多个传递 效果&#xff1a; 点击两个修改按钮后 4、使用defineModel简化父子传值 其他代码跟…

【postgresql】时间函数和操作符

日期/时间操作符 加减操作符&#xff1a; 和 - 可以用于日期、时间、时间戳和时间间隔的加减操作。 SELECT 2024-01-01::date INTERVAL 1 day as "date"; ; -- 结果&#xff1a;2024-01-02SELECT 2024-01-01 12:00:00::timestamp - INTERVAL 2 hours as "…

CSS上下悬浮特效

要实现一个上下悬浮的特效&#xff0c;可以使用CSS的keyframes规则和动画属性。以下是一个简单的示例&#xff1a; 代码示例 /* 定义一个名为floating的动画 */ keyframes floating {0% {transform: translateY(0); /* 初始位置 */}50% {transform: translateY(-4px); /* 向上…

KALI使用MSF攻击安卓设备

这期是kali使用MSF进行安卓渗透的保姆级别教程&#xff0c;话不多说&#xff0c;直接开始。 准备材料&#xff1a; 1.装有kali的实体机或虚拟机&#xff08;这里用实体机进行演示&#xff09; 2.一台安卓10.0以下的手机 打开kali&#xff0c;先用ifconfig查看自己的kali IP地址…