递归(recursion)
递归定义
一个方法之间或间接的调用方法自身的行为。
回溯定义:
回溯:从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。
作用
帮助解决复杂问题:
递归可以解决复杂的算法问题,可以使代码更加简洁,还可以提供一种崭新的思路比如递归解决 (队列反转,波兰表达式计算等等)今天学习了递归解决走出迷宫问题,我在下面代码会展示,至于上面提到的队列反转和波兰表达式计算我会在日后加以补充!
调用原则
- 当程序执行的一个方法的时候,就会在内存栈空间中开辟一个独立的空间用于存储方法执行过程中产生的临时变量,这个空间称之为栈区的方法帧。(执行开辟,执行完毕回收)
- 每一个方法帧中的基本数据类型的变量是相互独立的,不会相互影响,如果方法中局部变量(包含形参)是引用数据类型,则存在对于引用类型的值的共享,因为引用数据类型是存储在堆空间中的
- 一个方法遇到return (执行完毕,也会void隐式 return ),就会返回到当前调用这个方法的位置。(即谁调用该方法,就返回给谁)
- 实现递归的必要条件就是必须向退出递归的条件逼近,否则会导致无限递归,堆栈内存饱和,JVM裂开 然后你的idea会告诉你----StackOverflowError(堆栈溢出错误);
走迷宫问题
要求:
从地图左上角,走到地图的右下角
规则:
0:为默认值,1为墙壁,2能走通,3,周围均不可达的坐标
我的策略:
先向下走,走不通或已走过,则向右走::
向右走,走不通或已走过,则向上走::
向上走,走不通或已走过,则向左走::
直到程序结束
如果都走不通的话,会将走不通的坐标标为3,程序结束;
初始:
1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 0 1 0 0 0 1
1 1 1 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1
结果:
1 1 1 1 1 1 1
1 2 2 2 0 0 1
1 3 1 2 0 0 1
1 1 1 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 2 2 1
1 1 1 1 1 1 1
过程
package com.xm;
/**
* Demo5:
* 递归走迷宫问题
* 0:为默认值,1为墙壁,2能走通,3,到达不了
* map.length 行数,map[1].length 第二行的列数
*
* @author IlffQ
* 2022/8/2
*/
public class Demo5 {
public static void main(String[] args) {
int[][] map = new int[8][7];
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[map.length - 1][i] = 1;
}
for (int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][map[i].length - 1] = 1;
}
map[3][1] = map[3][2] = map[2][2] = 1;
showMap(map);
setWay(map, 1, 1);
System.out.println("-----------------");
showMap(map);
}
public static boolean setWay(int[][] map, int i, int j) {
if (map[6][5] == 2)
return true;
if (map[i][j] == 0) {
map[i][j] = 2;
//下
if (setWay(map, i + 1, j)) {
return true;
}
//右
if (setWay(map, i, j + 1)) {
return true;
}
//上
if (setWay(map, i - 1, j)) {
return true;
}
//左
if (setWay(map, i, j - 1)) {
return true;
}
else {
map[i][j] = 3;
return false;
}
}
return false;
}
/**
* 展示地图
*
* @param map 地图结构
*/
private static void showMap(int[][] map) {
for (int[] ints : map) {
for (int anInt : ints) {
System.out.print(anInt + " ");
}
System.out.println();
}
}
}
总结
回溯思想运用于走迷宫问题的话,通俗来说,就是,从当前位置出发,如果向某一个方法无法前进,回溯到上一个递归方法,改变方向继续执行。 如果四周被墙封闭,那么从当前位置探路,多次递归【if 代码块之外会返回false,if(递归方法)就会防止继续递归】后将走过的坐标赋值为3,返回false最终程序结束
动手尝试:
我先把当前位置包围在墙壁中,地图初始值和执行结果如下图:
初始:
1 1 1 1 1 1 1
1 0 1 0 0 0 1
1 0 1 0 0 0 1
1 1 1 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1
结束:
1 1 1 1 1 1 1
1 3 1 0 0 0 1
1 3 1 0 0 0 1
1 1 1 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1
代码如下,自己调试去吧,贴纸们!!!
package com.xm;
/**
* Demo5:
* 递归走迷宫问题
* 0:为默认值,1为墙壁,2能走通,3,到达不了
* map.length 行数,map[1].length 第二行的列数
*
* @author IlffQ
* 2022/8/2
*/
public class Demo5 {
public static void main(String[] args) {
int[][] map = new int[8][7];
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[map.length - 1][i] = 1;
}
for (int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][map[i].length - 1] = 1;
}
map[3][1] = map[3][2] = map[2][2] = map[1][2] = 1;
showMap(map);
setWay(map, 1, 1);
System.out.println("-----------------");
showMap(map);
}
public static boolean setWay(int[][] map, int i, int j) {
if (map[6][5] == 2)
return true;
if (map[i][j] == 0) {
map[i][j] = 2;
//下
if (setWay(map, i + 1, j)) {
return true;
}
//右
if (setWay(map, i, j + 1)) {
return true;
}
//上
if (setWay(map, i - 1, j)) {
return true;
}
//左
if (setWay(map, i, j - 1)) {
return true;
}
else {
map[i][j] = 3;
return false;
}
}
return false;
}
/**
* 展示地图
*
* @param map 地图结构
*/
private static void showMap(int[][] map) {
for (int[] ints : map) {
for (int anInt : ints) {
System.out.print(anInt + " ");
}
System.out.println();
}
}
}
8皇后问题
问题描述:
八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当n = 1或n ≥ 4时问题有解。
策略:
递归和回溯
核心逻辑:
核心逻辑 1:当前行中无法插入的情况下,会进行回溯到上一个方法帧中的for 循环中的map[n]= i,此时i已经进行了++操作,后继续执行,当前循环中不冲突的情况下,继续往下递归。
核心逻辑 2:棋子执行完最后一行后,调用putQueen(n+1),打印后,会退出方法,回到调用这个方法的地方,就相当于回到了核心逻辑1描述的地方(for循环,并进行i++操作。继续判断冲突)
其他相关细节,参见代码注释
过程
package com.xm;
/**
* Demo6:8
* 皇后问题
* 核心逻辑 1:当前行中无法插入的情况下,会进行回溯到上一个方法帧中的for 循环中的map[n]= i,
* 此时i已经进行了++操作,后继续执行,当前循环中不冲突的情况下,继续往下递归。
* <p>
* 核心逻辑 2:棋子执行完最后一行后,调用putQueen(n+1),打印后,会退出方法,回到调用这个方法的地方,
* 就相当于回到了核心逻辑1描述的地方(for循环,并进行i++操作。继续判断冲突)
*
* @author IlffQ
* 2022/8/6
*/
public class EightQueen {
public static void main(String[] args) {
int[] map = new int[8];
// showMap(map);
putQueen(0, 8, map);
}
/**
* @param n 第几个棋子
* @param max 最大棋子个数
* @param map 地图
*/
private static void putQueen(int n, int max, int[] map) {
/*
递归的退出条件
所需要的棋子摆放完成
*/
if (n == max) {
showMap(map);
System.out.println("================================");
return;
}
/*
循环摆放棋子
*/
for (int i = 0; i < max; i++) {
//n代表第几行,map[n]代表第几列
map[n] = i;
if (!isClash(map, n)) {
putQueen(n + 1, max, map);
}
}
}
/**
* @param map
* @param n 第几个
* @return true 冲突,false不冲突
*/
private static boolean isClash(int[] map, int n) {
/*n代表第几行,i代表前边的棋子所在行 (相差行数)
map[n]代表第n列,map[i]代表前面的棋子的所在列 (相差行数)
第n个棋子先和第一个棋子位置进行判定,依次往下*/
for (int i = 0; i < n; i++) {
if (map[i] == map[n] || Math.abs(n - i) == Math.abs(map[n] - map[i]))
return true;
}
return false;
}
private static void showMap(int[] map) {
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map.length; j++) {
if (j == map[i]) {
System.out.print(" ■ ");
}
else
System.out.print(" □ ");
}
System.out.println();
}
}
}
结论
共有92种排列方法
输出样例:
■ □ □ □ □ □ □ □
□ □ □ □ □ ■ □ □
□ □ □ □ □ □ □ ■
□ □ ■ □ □ □ □ □
□ □ □ □ □ □ ■ □
□ □ □ ■ □ □ □ □
□ ■ □ □ □ □ □ □
□ □ □ □ ■ □ □ □
================================
■ □ □ □ □ □ □ □
□ □ □ □ □ □ ■ □
□ □ □ ■ □ □ □ □
□ □ □ □ □ ■ □ □
□ □ □ □ □ □ □ ■
□ ■ □ □ □ □ □ □
□ □ □ □ ■ □ □ □
□ □ ■ □ □ □ □ □
==============================
共92个