递归回溯问题总结(迷宫问题,匹配路线问题,王子找公主问题)
递归回溯问题
回溯法概述:
用回溯法解决的问题的所有选项可以形象的用树状图结构进行描述,在某一步有n个可能的选项.那么该步骤可以看成是树状结构中的一个节点,每个选项看成树中节点连接连接线,经过这些连接线到达该节点的n个子节点,树的叶节点对应着终结状态.如果在叶节点的状态满足题目中的约束条件,那么我们即找到了一个可行的解决方案;
如果叶节点的状态不满足约束条件,只能回溯到上一个节点再继续尝试其他的选项,如果上一个节点的所有可能的选项都已经试过,并且不能到达满足约束条件的终结状态,则再一次回溯到上一个节点.如果所有的节点的所有选项都已经尝试过仍然不能达到满足约束条件的终结状态,则此问题无解.
递归问题1:迷宫问题
有一个迷宫:用一个二维数组定义,其中1表示墙,不能从这里通过,0表示可以通过,判断我们从其中入口A是否可以走通到出口B,以及其中的路线(用2表示其中走的路线)
利用递归进行实现:
1.map表示含有墙壁和路障的地图
2.如果小球能够从map[1][1]的位置走到了map[6][5]的位置,说明能够走通
3.约定:当map[i][j]为0表示该点路径没有走过;1表示是墙;2表示该点通路可以走;3表示该点已经走过,但是走不通
4.在走迷宫的时候,需要制定一个策略:
策略一:下->右->上->左
策略二:上->右->下->左(改变条件判断的次序即可)
图解:
代码
public class MiGong {
public static void main(String[] args) {
//定义一个map:此处利用二维数组创建
int[][] map = new int[8][7];//八行七列的迷宫
//设置墙和路障
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
for (int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
//添加路障
map[3][1] = 1;
map[3][2] = 1;
//输出地图的情况
System.out.println("原始的地图为:");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
//使用递归回溯给小球找路
//其中,map为地图,1,1为开始找路的坐标位置
//Boolean flag = setWay01(map, 1, 1);
Boolean flag = setWay02(map, 1, 1);
if (flag) {
System.out.println("找到走出迷宫的路线:");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}else {
System.out.println("未找到走出迷宫的路线");
}
}
//找路的方法
//若找到,则返回true,若未找到,则返回false
private static Boolean setWay01(int[][] map, int i, int j) {
if (map[6][5] == 2) {
//表示已经找到终点,2表示该路为通路
return true;
}else {
if (map[i][j] == 0) {//表示当前该点还没有走过
//则按照定义的策略进行找路
map[i][j] = 2;//先假设该点为通路
if (setWay01(map,i + 1,j)){//向下走若能走通
return true;
}else if (setWay01(map,i,j + 1)){//向右走若能走通
return true;
}else if (setWay01(map,i - 1,j)){//向上走若能走通
return true;
}else if (setWay01(map,i,j - 1)){//向左走若能走通
return true;
}else {
//若四种方式都无法走通
//将map[i][j]设置为3:表示该点已经走过,但是走不通
map[i][j] = 3;
return false;//返回false该路不通,回溯上一个函数
}
}else {//如果该点不为0:为1,2,3
return false;//表示该路不通
}
}
}
//策略2:上->右->下->左
private static Boolean setWay02(int[][] map, int i, int j) {
if (map[6][5] == 2) {
//表示已经找到终点,2表示该路为通路
return true;
}else {
if (map[i][j] == 0) {//表示当前该点还没有走过
//则按照定义的策略进行找路
map[i][j] = 2;//先假设该点为通路
if (setWay02(map,i - 1,j)){//向上走若能走通
return true;
}else if (setWay02(map,i,j + 1)){//向右走若能走通
return true;
}else if (setWay02(map,i + 1,j)){//向下走若能走通
return true;
}else if (setWay02(map,i,j - 1)){//向左走若能走通
return true;
}else {
//若四种方式都无法走通
//将map[i][j]设置为3:表示该点已经走过,但是走不通
map[i][j] = 3;
return false;//返回false该路不通
}
}else {//如果该点不为0:为1,2,3
return false;//表示该路不通
}
}
}
}
递归问题2:匹配路线问题
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。
例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例 1:
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = "ABCCED“
输出:true
示例 2:
输入:board = [[“a”,“b”],[“c”,“d”]], word = "abcd“
输出:false
提示:1 <= board.length <= 2001 <= board[i].length <= 200
思路:
这类递归回溯问题:即类似通常的迷宫问题,区别在于迷宫问题会给予你一个起始位置,但是此处的查看路线是否符合问题,没有定义所谓的起始坐标,我们需要进行所有位置的匹配,然后对所有位置中符合要求的进行递归回溯,最终观察是否满足其要求,若最终按照题设给定的word顺序能够通过上下左右走通的话,即表示该矩阵中确实存在一条包含word字符串所有字符的路径,否则,则不包含word字符串的路径
图解
代码实现
package com.bingym.problem12;
import java.util.Arrays;
public class FindPath {
/*
* 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。
* 路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。
* 如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子
* 示例:
* 示例 1:
* 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED“
* 输出:true
* 示例 2:
* 输入:board = [["a","b"],["c","d"]], word = "abcd“
* 输出:false
* 提示:1 <= board.length <= 2001 <= board[i].length <= 200
* */
public static void main(String[] args) {
char[][] board = {
{'A','B','C','E'},
{'S','F','C','S'},
{'A','D','E','E'}
};
String word = "ABCCED";
FindPath findPath = new FindPath();
boolean isExist = findPath.exist(board, word);
if (isExist) {
System.out.println("矩阵中存在一条包含" + word + "所有字符的路径");
}else {
System.out.println("矩阵中不存在一条包含" + word +"所有字符的路径");
}
}
public boolean exist(char[][] board, String word) {
char[] steps = word.toCharArray();
//因为相对于迷宫的起点固定问题,这里的任意位置都可以看做起点
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
//只有存在一个匹配steps的线路规划,则说明存在一条这样的路径
if (setWay(board,i,j,0,steps)) {
return true;//说明该步骤是可行的
}
}
}
//for循环结束,还没找到,则说明不存在线路
return false;
}
//方法1:
private boolean setWay(char[][] board, int i, int j, int k, char[] steps) {
if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != steps[k]) {
//如果递归调用过程中:
//出现i或者j越界问题,或者当前走的board[i][j] 不等于steps数组此时的值
return false;
}
if (k == steps.length - 1) {
//说明已经完全匹配steps路径,走完了路径的所有点
return true;
}
//输入setWay终止的条件
//记录当前走的点:board[i][j]
char temp = board[i][j];
//将此时board[i][j] = '#':表示已经走过
board[i][j] = '#';
//然后分别向向左、右、上、下移动一格是否匹配steps路径
if (setWay(board,i,j-1,k+1,steps)) {//向左进行递归
board[i][j] = temp;
return true;
}else if(setWay(board,i,j + 1,k+1,steps)) {//向右进行递归
board[i][j] = temp;
return true;
}else if (setWay(board,i - 1,j,k+1,steps)) {//向下进行递归
board[i][j] = temp;
return true;
}else if (setWay(board,i + 1,j,k+1,steps)) {//向上进行递归
board[i][j] = temp;
return true;
}else {
//无论该路通不通,将board[i][j]设置回原值
board[i][j] = temp;
return false;
}
}
//方法2:
private boolean setWaySimple(char[][] board, int i, int j, int k, char[] steps) {
if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != steps[k]) {
//如果递归调用过程中:
//出现i或者j越界问题,或者当前走的board[i][j] 不等于steps数组此时的值
return false;
}
if (k == steps.length - 1) {
//说明已经完全匹配steps路径,走完了路径的所有点
return true;
}
//输入setWay终止的条件
//记录当前走的点:board[i][j]
char temp = board[i][j];
//将此时board[i][j] = '#':表示已经走过
board[i][j] = '#';
//分别从左,右,上,下进行递归的简单版本
boolean isExist = setWaySimple(board,i,j-1,k+1,steps) || setWaySimple(board,i,j + 1,k+1,steps)
|| setWaySimple(board,i - 1,j,k+1,steps) || setWaySimple(board,i + 1,j,k+1,steps);
//将board[i][j]的值还原
board[i][j] = temp;
//返回查找的结果
return isExist;
}
}
递归问题3:王子找公主问题
问题:在一个地图中,’ . ‘表示可以通行,’ # ‘表示不能通行,王子的位置为S,公主的位置为E,针对输入的数据,判断王子是否可以能够到达公主的位置.
实现1:如果可以输出’‘YES’’,否则输出"NO".
实现2:不仅输出能够到达公主的位置,并将王子到公主的路线地图中用Y进行表示
代码实现1
import java.util.Arrays;
public class FindPrincess {
/*
* 王子找公主问题:利用递归回溯解决该问题
*此处输出能否找到公主
* */
public static void main(String[] args) {
char[][] board = {
{'.','.','.','S','.'},
{'.','#','#','#','.'},
{'#','#','E','.','.'}
};
FindPrincess solution = new FindPrincess();
boolean isExist = solution.findPrincess(board);
if(isExist) {
System.out.println("YES");
}else {
System.out.println("NO");
}
}
//查找公主的方法,返回:true(能找到);false(不能找到)
public boolean findPrincess(char[][] board) {
//记录王子位置S和公主位置E
//王子的坐标:i=0;j=3
//公主的坐标:board[i][j] = 'E';
Boolean isExist = setWay(board,0,3);
return isExist;
}
//底层调用的核心方法
private Boolean setWay(char[][] board, int i, int j) {
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] == '#' || board[i][j] == 'Y') {
return false;
}
if (board[i][j] == 'E') {
//表示找到了公主
return true;
}
//此时剩下只有:'.'(通路),'S'(王子位置)
char temp = board[i][j];//记录当前的值
//记录当前的值为已访问
board[i][j] = 'Y';
//分别进行向下,向上,向左,向右进行递归找路线的调用
boolean flag = setWay(board,i - 1,j) || setWay(board,i + 1,j) || setWay(board,i,j - 1) || setWay(board,i,j + 1);
board[i][j] = temp; //若此时将boad[i][j] = temp;则开始的地图并未放生变化;否则将会记录王子(S)到公主(E)所有走过的路线的点为'Y'
return flag;
}
}
代码实现2
import java.util.Arrays;
public class FindPrincess02 {
/*
* 王子找公主问题:利用递归回溯解决该问题
* 此处输出王子能够找到公主,如果能够找到,将王子找到公主的路线用'Y'进行标注
* */
public static void main(String[] args) {
char[][] board = {
{'.','.','.','.','.'},
{'.','#','#','S','.'},
{'.','.','E','#','.'}
};
FindPrincess02 solution = new FindPrincess02();
boolean isExist = solution.findPrincess(board);
System.out.println(isExist);
for (char[] chars : board) {
System.out.println(Arrays.toString(chars));
}
}
public boolean findPrincess(char[][] board) {
//记录王子位置S和公主位置E
Boolean isExist = setWay(board,1,3);
return isExist;
}
private Boolean setWay(char[][] board, int i, int j) {
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] == '#' || board[i][j] == 'Y') {
return false;
}
if (board[i][j] == 'E') {
//表示找到了公主
return true;
}
//此时剩下只有:'.'(通路),'S'(王子位置)
//记录当前的值为已访问
char temp = board[i][j];
if (board[i][j] != 'S') {
board[i][j] = 'Y';
}
//分别进行向下,向上,向左,向右进行递归找路
if (setWay(board,i - 1,j)) {
return true;
}else if (setWay(board,i + 1,j)) {
return true;
}else if (setWay(board,i,j - 1)) {
return true;
}else if (setWay(board,i,j + 1)) {
return true;
}else {
board[i][j] = temp;
return false;
}
}
}