探讨回溯算法的秘密(Java实现)

深入探讨回溯算法及Java实现

介绍:
回溯算法是一种经典的递归算法,用于解决在给定约束条件下的搜索问题。它通过尝试所有可能的解决方案,并在不满足约束条件的情况下回溯到上一步,继续尝试其他可能的解决方案。在本文中,我们将深入探讨回溯算法的原理,并使用Java代码来解决一个典型的回溯问题。


回溯算法概述

回溯算法是一种基于搜索的算法,常用于解决组合优化、排列组合、迷宫问题、八皇后等各种问题。它的基本思想是通过不断尝试所有可能的解决方案,并在满足特定条件时继续搜索,否则回溯到上一步。

回溯算法的一般框架如下:

  1. 定义问题的解空间:确定问题的解是什么,以及如何表示解。

  2. 确定约束条件:定义哪些解是可行的,需要满足的条件。

  3. 确定搜索顺序:确定搜索解空间的顺序,可以是深度优先搜索、广度优先搜索等。

  4. 编写回溯函数:编写一个递归函数,用于搜索解空间并进行回溯。

  5. 处理结果:根据需要,处理找到的解或输出最优解。

典型问题:组合求和

现在,我们将使用回溯算法解决一个典型的问题:给定一组候选数和一个目标数,找出候选数中所有可以组合为目标数的唯一组合。每个候选数可以无限次使用。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class CombinationSum {public List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> result = new ArrayList<>();List<Integer> combination = new ArrayList<>();Arrays.sort(candidates); // 对候选数进行排序backtrack(result, combination, candidates, target, 0);return result;}private void backtrack(List<List<Integer>> result, List<Integer> combination, int[] candidates, int target, int start) {if (target == 0) { // 找到一个满足条件的组合result.add(new ArrayList<>(combination));return;}for (int i = start; i < candidates.length; i++) {if (candidates[i] > target) // 剪枝,如果当前候选数大于目标数,则终止本次循环break;combination.add(candidates[i]); // 选择当前候选数backtrack(result, combination, candidates, target - candidates[i], i); // 递归搜索combination.remove(combination.size() - 1); // 撤销选择}}public static void main(String[] args) {CombinationSum solution = new CombinationSum();int[] candidates = {2, 3, 6, 7};int target = 7;List<List<Integer>> combinations = solution.combinationSum(candidates, target);System.out.println(combinations);}
}

在上面的代码中,我们使用backtrack方法进行回溯。在每个递归调用中,我们选择一个候选数,将其添加到当前组合中,然后递归调用backtrack方法,继续搜索,直到找到满足条件的组合或无法继续搜索。如果找到一个满足条件的组合,我们将其添加到结果列表中。最后,我们输出结果列表。


#N皇后问题

N皇后问题是经典的回溯算法问题,它要求在N×N的棋盘上放置N个皇后,使得它们互相之间不能攻击到对方。即任意两个皇后不能处于同一行、同一列或同一对角线上。在本文中,我们将深入探讨N皇后问题,并给出一个使用回溯算法解决该问题的详细解决方案。

N皇后问题是一个经典的组合优化问题,旨在找到在N×N的棋盘上放置N个皇后的所有合法方案。在解决该问题时,需要满足以下条件:

  1. 任意两个皇后不能处于同一行。
  2. 任意两个皇后不能处于同一列。
  3. 任意两个皇后不能处于同一对角线。

由于皇后的特殊移动方式(可以横向、纵向和斜向移动),这些条件确保了在任何解中,皇后之间不会相互攻击。

解决方案

下面是一个使用回溯算法解决N皇后问题的详细解决方案,其中的思路是递归地搜索合法的解空间。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class NQueens {public List<List<String>> solveNQueens(int n) {List<List<String>> result = new ArrayList<>();char[][] board = new char[n][n];for (char[] row : board) {Arrays.fill(row, '.');}backtrack(result, board, 0);return result;}private void backtrack(List<List<String>> result, char[][] board, int row) {if (row == board.length) { // 如果已经遍历完了所有行,说明找到了一个解result.add(constructSolution(board)); // 将当前棋盘配置添加到结果中return;}for (int col = 0; col < board.length; col++) {if (isValid(board, row, col)) { // 检查当前位置是否可以放置皇后board[row][col] = 'Q'; // 选择放置皇后backtrack(result, board, row + 1); // 递归搜索下一行board[row][col] = '.'; // 撤销选择,回溯到上一状态}}}private boolean isValid(char[][] board, int row, int col) {int n = board.length;// 检查当前列是否有皇后for (int i = 0; i < row; i++) {if (board[i][col] == 'Q') {return false;}}// 检查左上方是否有皇后for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {if (board[i][j] == 'Q') {return false;}}// 检查右上方是否有皇后for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {if (board[i][j] == 'Q') {return false;}}return true;}private List<String> constructSolution(char[][] board) {List<String> solution = new ArrayList<>();for (char[] row : board) {solution.add(new String(row));}return solution;}public static void main(String[] args) {NQueens solution = new NQueens();int n = 4;List<List<String>> solutions = solution.solveNQueens(n);for (List<String> solutionBoard : solutions) {for (String row : solutionBoard) {System.out.println(row);}System.out.println();}}
}

在这段代码中,我们使用backtrack方法进行回溯。在每个递归调用中,我们尝试在当前行的每个位置放置一个皇后,并检查是否满足条件。如果满足条件,我们在该位置放置皇后,并递归调用backtrack方法,继续搜索下一行的解决方案。如果搜索完成后,我们得到了一个合法的解,我们将其添加到结果列表中。

isValid方法中,我们检查当前位置是否满足N皇后问题的条件。我们需要检查当前列、左上方和右上方是否有皇后。

constructSolution方法中,我们将棋盘转换为字符串列表,以符合问题的输出格式。

main方法中,我们创建一个NQueens对象,并调用solveNQueens方法来解决N皇后问题,并打印结果。

示例输出

以下是在4×4棋盘上解决N皇后问题的一个解的示例输出:

.Q..
...Q
Q...
..Q...Q.
Q...
...Q
.Q..

这个解表示第一行的皇后放置在第2列,第二行的皇后放置在第4列,第三行的皇后放置在第1列,第四行的皇后放置在第3列。这是一个满足N皇后问题条件的合法解。

zhiwenti

#数组取值问题:

给定一个正整数数组 candidates 和一个目标值 target,要求找到数组中所有唯一的组合,使得组合中元素的和等于目标值 target。

每个数字在组合中可以被重复使用,而组合中的数字可以按任意顺序排列。

import java.util.ArrayList;
import java.util.List;public class CombinationSum {public static List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> result = new ArrayList<>(); // 存储结果的列表List<Integer> combination = new ArrayList<>(); // 当前组合的列表backtrack(result, combination, candidates, target, 0); // 调用回溯函数进行搜索return result;}private static void backtrack(List<List<Integer>> result, List<Integer> combination, int[] candidates, int target, int start) {if (target < 0) {return; // 如果目标值小于0,说明组合的和已经超过了目标值,直接返回} else if (target == 0) {result.add(new ArrayList<>(combination)); // 如果目标值等于0,说明当前组合的和正好等于目标值,将组合添加到结果列表中} else {for (int i = start; i < candidates.length; i++) {combination.add(candidates[i]); // 将当前候选元素添加到组合中backtrack(result, combination, candidates, target - candidates[i], i); // 递归调用回溯函数,目标值更新为target - candidates[i],起始索引保持不变combination.remove(combination.size() - 1); // 递归结束后,将添加的候选元素从组合中移除,以便尝试其他组合}}}public static void main(String[] args) {int[] candidates = {2, 3, 6, 7}; // 候选数组int target = 7; // 目标值List<List<Integer>> result = combinationSum(candidates, target); // 调用combinationSum方法获取结果for (List<Integer> combination : result) {System.out.println(combination); // 打印结果列表中的所有组合}}
}

在代码中,我们添加了注释以解释每个关键部分的作用:

  • result:存储结果的列表,其中每个元素是一个符合条件的组合。
  • combination:当前组合的列表,用于构建可能的组合。
  • backtrack方法:核心的回溯函数,用于搜索满足条件的组合。
  • if (target < 0):如果目标值小于0,说明当前组合的和已经超过了目标值,直接返回。
  • else if (target == 0):如果目标值等于0,说明当前组合的和正好等于目标值,将组合添加到结果列表中。
  • for (int i = start; i < candidates.length; i++):从起始索引开始遍历候选数组,尝试每个候选元素。
  • combination.add(candidates[i]):将当前候选元素添加到组合中。
  • backtrack(result, combination, candidates, target - candidates[i], i):递归调用回溯函数,目标值更新为target - candidates[i],起始索引保持不变,因为可以重复使用候选元素。
  • combination.remove(combination.size() - 1):递归结束后,将添加的候选元素从组合中移除,以便尝试其他组合。
  • main方法:测试代码,使用示例输入调用combinationSum方法,并打印结果列表中的所有组合。

#数组取值问题变形

给定一个正整数数组 candidates 和一个目标值 target,要求找到数组中所有唯一的组合,使得组合中元素的和等于目标值 target。

每个数字在组合中不可以被重复使用,而组合中的数字可以按任意顺序排列。

package org.example.LeetCodeStudey.backtrack;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class CombinationSumPro {public static List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> result = new ArrayList<>();List<Integer> combination = new ArrayList<>();Arrays.sort(candidates); // 需要先对数组进行排序backtrack(result, combination, candidates, target, 0);return result;}private static void backtrack(List<List<Integer>> result, List<Integer> combination, int[] candidates, int target, int start) {if (target < 0) { // 如果目标值小于0,说明当前组合不合法,直接返回return;} else if (target == 0) { // 如果目标值等于0,说明当前组合满足条件,将其添加到结果中result.add(new ArrayList<>(combination));} else {for (int i = start; i < candidates.length; i++) {if (i > start && candidates[i] == candidates[i - 1]) { // 避免重复使用相同的元素continue;}combination.add(candidates[i]); // 将当前元素添加到组合中backtrack(result, combination, candidates, target - candidates[i], i + 1); // 递归调用,更新目标值和起始位置combination.remove(combination.size() - 1); // 回溯,移除最后一个添加的元素,以便尝试其他可能的组合}}}public static void main(String[] args) {int[] candidates = {10, 1, 2, 7, 6, 1, 5};int target = 8;List<List<Integer>> result = combinationSum(candidates, target);for (List<Integer> combination : result) {System.out.println(combination);}}
}

这段代码解决了组合总和问题,即给定一组候选数字(candidates)和一个目标值(target),找出所有候选数字的组合,使得它们的和等于目标值。在这个问题中,每个数字可以重复使用,且结果中不能包含重复的组合。

代码中的方法combinationSum用于计算出所有满足条件的组合。它首先对候选数组进行排序,以便在回溯过程中避免重复的组合。

backtrack方法中,我们使用回溯算法来搜索所有可能的组合。它有五个参数:

  • result是结果列表,用于保存所有满足条件的组合;
  • combination是当前的组合,用于保存临时结果;
  • candidates是排序后的候选数组;
  • target是目标值,表示剩余需要凑齐的值;
  • start是起始位置,用于避免重复使用相同的元素。

在回溯过程中,我们依次遍历候选数组中的元素。对于每个元素,如果它大于剩余的目标值,说明当前组合不合法,直接返回。如果它等于剩余的目标值,说明当前组合满足条件,将其添加到结果中。否则,我们将当前元素添加到组合中,更新剩余的目标值为 target - candidates[i],并递归调用backtrack方法,从当前位置的下一个位置开始(i + 1)。递归调用返回后,我们需要进行回溯,移除最后一个添加的元素,以便尝试其他可能的组合。

main方法中,我们使用示例数据调用combinationSum方法,并打印结果。

对于给定的示例数据 [10, 1, 2, 7, 6, 1, 5] 和目标值 8,运行结果如下:

[1, 1, 6]
[1, 2, 5]
[1, 7]
[2, 6]

总结

1,回溯算法是一种强大的搜索算法,可以用于解决各种组合优化、排列组合、迷宫问题等。它的基本思想是通过尝试所有可能的解决方案,并在满足约束条件的情况下继续搜索,否则回溯到上一步。

2,在本文中,我们深入探讨了回溯算法的原理,并使用Java代码解决了一个典型的回溯问题:组合求和。该问题要求找出一组候选数中所有可以组合为目标数的唯一组合。

3,通过编写回溯函数并遵循回溯算法的框架,我们实现了一个能够找到所有满足条件的组合的解决方案。在代码中,我们使用了递归调用和剪枝技巧,以减少搜索空间并提高算法效率。

4,回溯算法在实际应用中非常有用,但也需要注意一些问题。由于回溯算法的时间复杂度通常很高,因此在处理大规模问题时,需要考虑性能优化和剪枝策略。此外,合理定义问题的解空间和约束条件也对算法的效率和正确性至关重要。

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

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

相关文章

Linux学习记录——삼십구 数据链路层协议

文章目录 1、了解数据链路层2、认识以太网3、认识MAC地址4、以太网报文5、局域网通信原理1、基本原理2、数据碰撞3、交换机4、ARP协议5、RARP协议6、局域网中间人 6、DNS&#xff08;简单介绍&#xff09;7、ICMP协议1、报文2、traceroute命令 7、NAT技术1、基本过程2、NAPT3、…

GO项目自动化-根据库表字段自动生成API

根据库表字段自动生成API 一个项目开发离不开数据库的增删改查&#xff0c;API功能也基本围绕着表的CRUD&#xff1a;增加(Create)、读取(Read)、更新(Update)和删除(Delete)。每个表写一个CRUD&#xff0c;方法都差不多&#xff0c;逻辑也非常相似。 那么有没有可能根据表结构…

C++力扣题目111--二叉树的最小深度

力扣题目链接(opens new window) 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明: 叶子节点是指没有子节点的节点。 示例: 给定二叉树 [3,9,20,null,null,15,7], 返回它的最小深度 2 思路 看完了这篇104.二…

Java入门IDEA基础语法

1&#xff1a;Java入门 1.1 Java简介 Java是什么&#xff1a; Java是一门非常优秀的计算机语言 语言&#xff1a;人与人交流沟通的表达方式 计算机语言&#xff1a;人与计算机之间进行信息交流沟通的一种特殊语言 Java之父&#xff1a;詹姆斯高斯林&#xff08;James Gosli…

履践致远 载誉前行 | 甄知科技获评多项荣誉资质认定!

砥砺深耕 履践致远 甄知科技不断精进 持续成长 获评多项荣誉资质认定 23年4月 甄知成功入库科技型中小企业名单 2023年4月&#xff0c;上海市科技技术委员会公布2023年第二批入库科技型中小企业名单&#xff0c;依据《科技型中小企业评价办法》等要求&#xff0c;经各级评价工…

《尚贤达猎头网站流量统计模块》,通过HTTP自定义模块实时获取asp.net网站访问流量,并保存到数据库

开发了个网站流量统计模块&#xff0c;实时获取asp.net网站访问流量&#xff0c;并保存到数据库。 一、功能&#xff1a; 通过HTTP自定义模块实时获取网站流量 二、支持平台&#xff1a;windowsIIS 三、安装方法&#xff1a; 1、将文件www.sunsharer.cn.dll复制到网站bin目录下…

汽车出海业务专业术语

引言 本文是笔者在做中国汽车出口欧洲业务的信息化建设过程,积累的一些专业术语注解,供诸位参考交流。 专业术语清单 报关   报关是指出口货物的所有者或其代理人,根据中国海关法和相关法规,向海关申报货物的出口情况,并提交相关单证,以便海关对货物进行监管和征税。…

什么是算法的时间复杂度?

一、问题 算法的时间复杂度是评测算法好坏的主要指标&#xff0c;那么什么是算法的时间复杂度呢&#xff1f; 二、解答 算法的时间复杂度度量主要是计算⼀个算法所⽤的时间&#xff0c;算法所⽤的时间主要包括程序编译时间和运⾏时间。由于⼀个算法⼀旦编译成功就可以多次运⾏…

孩子用什么样的灯对眼睛没有伤害?分享最合适孩子的护眼台灯

为人父母以后&#xff0c;孩子健康成长一定是摆放在首位的&#xff0c;随着孩子慢慢长大&#xff0c;步入更高的年级&#xff0c;作业课程也在随之增多。不少孩子哪怕夜色已经降临&#xff0c;仍就伏案在桌子上完成没有做完的功课&#xff0c;作为父母的我们不得不担心孩子的视…

记录一下Canal的错误,主要是top.javatool.canal.client.util下的StringConvertUtil引起的

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 由于数据库的一个localdatetime字段是空的&#xff0c; 然后修改数据库数据同步canal的时候报了这个错误&#xff1a; Caused by: java.lang.IllegalArgumentException: Can not set java.time.LocalD…

浏览器输入一个域名的解析过程

目录 从输入一个域名的解析过程 以www.baidu.com为例子 本地缓存和hosts文件 mDNS和LLMNR NBT-NS 路由器广播 Root域名服务器 顶级域名服务器 目标域名服务器 DNS解析完成 操作系统发起TCP连接&#xff1a; TCP三次握手&#xff1a; TCP连接的建立采用经典的三次握手过程&#…

2023年全国职业院校技能大赛软件测试赛题—单元测试卷③

单元测试 一、任务要求 题目1&#xff1a;输入一个大写字母一个小写字母。根据输入的第一个字母和英文周几单词的第一个大写字母判断是周几&#xff0c;如果无法根据第一个大写字母判断&#xff0c;则继续根据输入的第二个小写字母进行判断&#xff0c;最终返回正确的英文周几…

第88讲:XtraBackup实现增量数据备份以及故障恢复的应用实践

文章目录 1.XtraBackup增量备份恢复的概念2.XBK增量备份语法3.使用XBK实现数据库的增量备份3.1.周日全量备份数据库3.2.周一产生增量数据并进行增量备份3.3.周二产生增量数据并进行增量备份3.4.查看两次增量以及全量的备份文件3.5.核对全量和增量备份的准确性 4.使用XBK通过增量…

Python的运算符和数据类型转换

Python的运算符&#xff1a; 算术运算符: 加法&#xff1a;减法&#xff1a;-乘法&#xff1a;*除法&#xff1a;/取余&#xff1a;%幂运算&#xff1a;**整除&#xff1a;// 比较运算符: 等于&#xff1a;不等于&#xff1a;!大于&#xff1a;>小于&#xff1a;<大于等于…

WPF真入门教程27--项目案例--设备数据实时监测

1、上图看效果 今天要做的一个案例是这样的效果&#xff0c;它能实时监测车间设备有关数据&#xff0c;并以表格和图形显示在界面上&#xff0c;这个比上个案例要复杂些&#xff0c;颜值也高些&#xff0c;通过这个来巩固wpf的技能&#xff0c;用到了命令绑定&#xff0c;样式…

【数据库】视图索引执行计划多表查询笔试题

文章目录 一、视图1.1 概念1.2 视图与数据表的区别1.3 优点1.4 语法1.5 实例 二、索引2.1 什么是索引2.2.为什么要使用索引2.3 优缺点2.4 何时不使用索引2.5 索引何时失效2.6 索引分类2.6.1.普通索引2.6.2.唯一索引2.6.3.主键索引2.6.4.组合索引2.6.5.全文索引 三、执行计划3.1…

uniapp运行自定义底座到真机没反应

同步资源失败&#xff0c;未得到同步资源的授权&#xff0c;请停止运行后重新运行&#xff0c;并注意手机上的授权提示。 如果此时手机没有任何反应&#xff0c;请检查自定义基座是否正确;如果是离线制作的自定义基座包&#xff0c; 请检查离线包制作是否正确。 网上各种查找报…

C++力扣题目513找树左下角的值

给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1示例 2: 输入: [1,2,3,4,null,5,6,null,null,7] 输出: 7 思路 本题要找出树的最后一行的最左边的值。此时大家应该想…

C#Stopwatch类高精度计时功能

Stopwatch 是 C# 中的一个类&#xff0c;用于测量时间间隔的工具。它提供了高精度的计时功能&#xff0c;可以用于性能测试、调试和其他需要测量时间的场景。 使用 Stopwatch 类&#xff0c;你可以执行以下操作&#xff1a; 启动计时器&#xff1a;使用 Start 方法启动计时器…

Verilog 状态机 示例

状态机设计&#xff1a;3 段式&#xff08;推荐&#xff09; 状态机设计如下&#xff1a; (0) 首先&#xff0c;根据状态机的个数确定状态机编码。利用编码给状态寄存器赋值&#xff0c;代码可读性更好。 (1) 状态机第一段&#xff0c;时序逻辑&#xff0c;非阻塞赋值&#xf…