递归应用场景
递归的概念
简单的说: 递归就是方法自己调用自己,每次调用时,传入不同的变量,递归有助于编程者解决复杂的问题,同时可以让代码变得简洁
递归调用机制
列举两个小案例,来帮助大家理解递归,如果学习过了,就回顾一下,没学习过,就好好看
- 打印问题
- 阶乘问题
案例
package com.dance.recursion;
public class Demo {
public static void main(String[] args) {
test(3);
// 阶乘案例 1 x 2 x 3 = 6
System.out.println(factorial(3));
}
/**
* 递归案例 1
* @param n 数字
*/
public static void test(int n) {
if(n > 2) {
// 递归位置
test(n - 1);
}
System.out.println("n = " + n);
}
/**
* 递归案例 2
* @param n 阶乘数
* @return 阶乘数
*/
public static int factorial(int n){
if(n==1){
return 1;
}
return factorial(n - 1) * n;
}
}
执行结果
n = 2
n = 3
6
递归能解决什么样问题
递归用于解决什么样的问题
- 各种数学问题如: 8皇后问题, 汉诺塔问题, 阶乘问题, 迷宫问题, 球和篮子问题(Google编程大赛)
- 各种算法中也会使用到递归, 比如快速排序, 二分查找,分治算法等
-
递归需要遵守的重要规则
执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的, 不会相互影响, 比如n变量
- 如果方法中使用的是引用类型的变量(比如数组), 就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近, 否则就是无线递归了, 出现StackOverflowError(栈溢出).死龟了: )
- 当一个方法执行完毕,或者遇到return ,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕
递归-迷宫问题(回溯算法)
迷宫问题
代码实现
```java package com.dance.recursion;
import java.util.Arrays;
/**
迷宫问题 */ public class Maze {
public static void main(String[] args) {
int[][] maze = createMaze(8, 7); setAppointWall(maze, 3, 1); setAppointWall(maze, 2, 2); setAppointWall(maze, 3, 2); setAppointWall(maze, 5, 2); setAppointWall(maze, 5, 3); setAppointWall(maze, 5, 4); setAppointWall(maze, 5, 5); // 初始迷宫 System.out.println("初始迷宫"); showMaze(maze); boolean b = setWay(maze, 1, 1, 6, 5); System.out.println("路线"); // 找到路线 后的 showMaze(maze);
}
/**
- 使用递归回溯来给小球找路
- 约定 当maze[startX][startY]
- case 0:
- 表示该点没有走过
- case 1:
- 表示墙 不能走
- case 2:
- 表示可以走
- case 3:
- 表示该点已经走过,但是走不通
- 策略
- 下 -> 右 -> 上 -> 左 *
- @param maze 迷宫
- @param x 起始x坐标
- @param y 起始y坐标
@return 是否找到, 找到返回true,没找到返回false */ public static boolean setWay(int[][] maze, int startX, int startY, int endX, int endY) { // 断言 不能从墙壁开始 if (maze[startX][startY] == 1 || maze[endX][endY] == 1) {
// 墙壁 return false;
} if (maze[endX][endY] == 2) {
// 表示已经走通了 return true;
} else {
if (maze[startX][startY] == 0) { // 没有走过 将当前节点设置为2 maze[startX][startY] = 2; // 向下走 x 轴 + 1 if (setWay(maze, startX + 1, startY, endX, endY)) { // 如果能走通 return true; // 向右走 y 轴 + 1 } else if (setWay(maze, startX, startY + 1, endX, endY)) { return true; // 向上走 x 轴 - 1 } else if (setWay(maze, startX - 1, startY, endX, endY)) { return true; // 向左走 y 轴 - 1 } else if (setWay(maze, startX, startY - 1, endX, endY)) { return true; } else { // 上下左右都不能走 说明该点走不通 maze[startX][startY] = 3; return false; } }else{ // 如果不是0, 可能是 2, 3 // 直接返回false return false; }
} }
/**
- 创建迷宫 *
- @param width 宽度
- @param height 高度
@return 迷宫 */ public static int[][] createMaze(int width, int height) { int[][] maze = new int[width][height]; setWall(maze); return maze; }
/**
- 指定位置生成墙壁 *
- @param maze 迷宫
- @param x x坐标
@param y y坐标 */ public static void setAppointWall(int[][] maze, int x, int y) { maze[x][y] = 1; }
/**
- 将迷宫的上下设置为墙壁, 使用1表示 *
@param maze 迷宫 */ private static void setWall(int[][] maze) { // 将上下设置为墙壁 Arrays.fill(maze[0], 1); Arrays.fill(maze[maze.length - 1], 1); // 将左右设置为墙壁 for (int i = 0; i < maze.length; i++) {
maze[i][0] = 1; maze[i][maze[i].length - 1] = 1;
} }
/**
- 展示迷宫 *
- @param maze 迷宫
*/
public static void showMaze(int[][] maze) {
for (int[] ints : maze) {
} }for (int anInt : ints) { System.out.print(anInt + "\t"); } System.out.println();
}
执行结果
```java
初始迷宫
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 1 1 1 1 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 3 3 1
1 3 1 2 3 3 1
1 1 1 2 3 3 1
1 2 2 2 3 3 1
1 2 1 1 1 1 1
1 2 2 2 2 2 1
1 1 1 1 1 1 1
对迷宫问题的讨论
- 小球得到的路径,和程序设置的找路策略有关,就是先上还是先下…
- 在得到小球的路径时,可以使用不同的策略,看看路径是不是有变化
- 测试回溯现象
- 思考: 如何求出最短路径? 思路 -> 代码实现
使用不同的策略,然后使用一个Map记录策略和步长,然后对比找出最优策略和最短路径
递归-八皇后问题(回溯算法)
八皇后问题
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例,该问题是国际西洋棋棋手马克思·贝瑟尔于1848年提出: 在8X8的国际象棋上摆放8个皇后,使其不能互相攻击, 即: 任意两个皇后不能处于同一行,同一列,同一条斜线上,问:有多少种摆法(92)
思路分析
- 第一个皇后先放第一行,第一列
- 第二个皇后放在第二行第一列,然后判断是否ok, 如果不ok, 继续放在第二列, 第三列,依次吧所有列都放完找到一个合适的
- 继续第三个皇后还是第一列,第二列……直到第八个皇后也能放在一个不冲突的位置,算是找到了一个正确的解
- 当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即,将第一个皇后,放到第一列的所有正确解,全部得到
- 然后回头第一个皇后放第二列,后面继续循环1,2,3,4的步骤
示意图
说明
理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法,用一个一维数组即可解决问题,arr[8] = {0,4,7,5,2,6,1,3} // 对应arr下标 表示第几行, 即第几个皇后 arr[i] = val, val表示第i+1个皇后,放在第i+1行的第val+1列代码实现
``` package com.dance.recursion;
import java.util.Arrays;
public class EightQueens {
/**
* 代表有8个皇后
*/
static int maxSize = 8;
/**
* 定义数组保存皇后位置的结果
* 例如: arr = {0,4,7,5,2,6,1,3}
* 下标 + 1 代表行(从下往上数)
* 值 + 1 代表列(从左往右数)
*/
int[] arr = new int[maxSize];
/**
* 统计解法数量
*/
static int count = 0;
/**
* 检查次数
*/
static int judgeCount = 0;
public static void main(String[] args) {
EightQueens eightQueens = new EightQueens();
eightQueens.checked(0);
System.out.println("总共有" + count + "中解法");
System.out.println("检查方法调用次数:" + judgeCount);
}
/**
* 输出皇后摆放的位置
*/
public void print() {
System.out.println(Arrays.toString(arr));
}
/**
* 判断放置的第n个皇后是否和之前摆放的皇后冲突
*
* @param n 第n个皇后
* @return 是否冲突, 冲突返回false, 不重复返回true
*/
private boolean judge(int n) {
judgeCount++;
// 循环之前的皇后
for (int i = 0; i < n; i++) {
// 取到前面的皇后
int i1 = arr[i];
// i1 == arr[n] 代表y轴冲突
// Math.abs(n - i) == Math.abs(arr[n] - arr[i]) 代表斜轴冲突
/*
* abs : 求绝对值
* 第一个皇后 第二个皇后第一次 第二个皇后第二次 第二个皇后第三次
* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0
* 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0
* 第三个皇后第一次 第三个皇后第二次 第三个皇后第三次 第三个皇后第四次
* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
* 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0
* 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0
* 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0
* 第三个皇后第五次
* 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0
* 0 0 0 0 1 0 0 0
* 0 0 1 0 0 0 0 0
* 1 0 0 0 0 0 0 0
* 第一个皇后放在了 arr = {0,.....} 的位置 也就是第一行,第一列
* 所以 判断 i < 0 不会走循环 直接可以放,因为当前棋盘上是空的
* 第二个皇后,第一次放在arr = {0,0,.....}的位置, 也就是第二行,第一列
* [1]所以 判断 i < 1 所以会走一次循环
* 开始走逻辑
* arr[i] == arr[n] => 0 == 0 条件成立 代表在同一列所以不能放
* 第二个皇后,第二次会放在arr = {0,1,.....}的位置, 也就是第二行,第二列
* ^[1]
* 0 == 1 条件不成立, 代表不在同一列, 继续走下一个条件
* Math.abs(n - i) == Math.abs(arr[n] - arr[i]) => Math.abs(1 - 0) == Math.abs(1 - 0)
* 计算Math.abs后 => 1 == 1 条件成立 代表在同一斜线所以不能放
* 第二个皇后,第三次会放在arr = {0,2,.....}的位置, 也就是第二行,第三列
* ^[1]
* Math.abs(n - i) == Math.abs(arr[n] - arr[i]) => Math.abs(1 - 0) == Math.abs(2 - 0)
* 计算Math.abs后 => 1 == 2 条件不成立 代表斜线不冲突,可以放
* 然后开始放第三个皇后,第一次会被放在arr = {0,2,0,.....},也就是第三行, 第一列
* 判断和第一个皇后同列,不能放(同列判断很好理解就不多写了)
* 第三个皇后,第二次会被放在arr = {0,2,1,.....}也就是,第三行m,第二列
* 和第一个皇后不同列, 然后计算斜线
* Math.abs(n - i) == Math.abs(arr[n] - arr[i]) => Math.abs(2 - 0) == Math.abs(1 - 0)
* 计算Math.abs后 => 2 == 1 条件不成立 代表斜线不冲突,可以放
* 但是还要计算和第二个皇后是否同列,同斜线
* 判断和第二个皇后不同列,然后计算斜线
* Math.abs(n - i) == Math.abs(arr[n] - arr[i]) => Math.abs(2 - 1) == Math.abs(1 - 2)
* 计算Math.abs后 => 1 == 1 条件成立 代表斜线冲突,不可以放
* 第三个皇后第三次会被放在arr = {0,2,2,.....} 也就是第三行,第三列
* 和第二个同列冲突,不能放
* 第三个皇后第四次会被放在arr = {0,2,3,.....} 也就是第三行,第四列
* 和第二个皇后成斜线,不能放
* 第三个皇后第五次会被放在arr = {0,2,4,....} 也就是第三行第五列
* 和第一个第二个皇后都不同列同斜线,可以放,后面持续递归..........
*/
if (i1 == arr[n] || Math.abs(n - i) == Math.abs(arr[n] - arr[i])) {
return false;
}
}
// 循环完成没有返回FALSE 代表不冲突返回TRUE
return true;
}
/**
* 放置第n个皇后
*
* @param n 第n个皇后
*/
private void checked(int n) {
if (n == maxSize) {
// 代表已经放到了第9个了
print();
count++;
return;
}
// 依次放入皇后,并判断是否冲突
for (int i = 0; i < maxSize; i++) {
// 先把当前的皇后 n 放到第一列 0
arr[n] = i;
// 判断当放置第n个皇后到第i列时,是否冲突
if (judge(n)) {
// 不冲突 开始递归放n + 1个 -> 一直到底9个停
// 如果放不到第9个,也就是放完,就会回溯上一个棋子后移后继续递归,一直到放完8个棋子
checked(n + 1);
}
// 冲突 , 循环进入下一个位置
}
}
}
执行结果
[0, 4, 7, 5, 2, 6, 1, 3] [0, 5, 7, 2, 6, 3, 1, 4] [0, 6, 3, 5, 7, 1, 4, 2] [0, 6, 4, 7, 1, 3, 5, 2] [1, 3, 5, 7, 2, 0, 6, 4] [1, 4, 6, 0, 2, 7, 5, 3] [1, 4, 6, 3, 0, 7, 5, 2] [1, 5, 0, 6, 3, 7, 2, 4] [1, 5, 7, 2, 0, 3, 6, 4] [1, 6, 2, 5, 7, 4, 0, 3] [1, 6, 4, 7, 0, 3, 5, 2] [1, 7, 5, 0, 2, 4, 6, 3] [2, 0, 6, 4, 7, 1, 3, 5] [2, 4, 1, 7, 0, 6, 3, 5] [2, 4, 1, 7, 5, 3, 6, 0] [2, 4, 6, 0, 3, 1, 7, 5] [2, 4, 7, 3, 0, 6, 1, 5] [2, 5, 1, 4, 7, 0, 6, 3] [2, 5, 1, 6, 0, 3, 7, 4] [2, 5, 1, 6, 4, 0, 7, 3] [2, 5, 3, 0, 7, 4, 6, 1] [2, 5, 3, 1, 7, 4, 6, 0] [2, 5, 7, 0, 3, 6, 4, 1] [2, 5, 7, 0, 4, 6, 1, 3] [2, 5, 7, 1, 3, 0, 6, 4] [2, 6, 1, 7, 4, 0, 3, 5] [2, 6, 1, 7, 5, 3, 0, 4] [2, 7, 3, 6, 0, 5, 1, 4] [3, 0, 4, 7, 1, 6, 2, 5] [3, 0, 4, 7, 5, 2, 6, 1] [3, 1, 4, 7, 5, 0, 2, 6] [3, 1, 6, 2, 5, 7, 0, 4] [3, 1, 6, 2, 5, 7, 4, 0] [3, 1, 6, 4, 0, 7, 5, 2] [3, 1, 7, 4, 6, 0, 2, 5] [3, 1, 7, 5, 0, 2, 4, 6] [3, 5, 0, 4, 1, 7, 2, 6] [3, 5, 7, 1, 6, 0, 2, 4] [3, 5, 7, 2, 0, 6, 4, 1] [3, 6, 0, 7, 4, 1, 5, 2] [3, 6, 2, 7, 1, 4, 0, 5] [3, 6, 4, 1, 5, 0, 2, 7] [3, 6, 4, 2, 0, 5, 7, 1] [3, 7, 0, 2, 5, 1, 6, 4] [3, 7, 0, 4, 6, 1, 5, 2] [3, 7, 4, 2, 0, 6, 1, 5] [4, 0, 3, 5, 7, 1, 6, 2] [4, 0, 7, 3, 1, 6, 2, 5] [4, 0, 7, 5, 2, 6, 1, 3] [4, 1, 3, 5, 7, 2, 0, 6] [4, 1, 3, 6, 2, 7, 5, 0] [4, 1, 5, 0, 6, 3, 7, 2] [4, 1, 7, 0, 3, 6, 2, 5] [4, 2, 0, 5, 7, 1, 3, 6] [4, 2, 0, 6, 1, 7, 5, 3] [4, 2, 7, 3, 6, 0, 5, 1] [4, 6, 0, 2, 7, 5, 3, 1] [4, 6, 0, 3, 1, 7, 5, 2] [4, 6, 1, 3, 7, 0, 2, 5] [4, 6, 1, 5, 2, 0, 3, 7] [4, 6, 1, 5, 2, 0, 7, 3] [4, 6, 3, 0, 2, 7, 5, 1] [4, 7, 3, 0, 2, 5, 1, 6] [4, 7, 3, 0, 6, 1, 5, 2] [5, 0, 4, 1, 7, 2, 6, 3] [5, 1, 6, 0, 2, 4, 7, 3] [5, 1, 6, 0, 3, 7, 4, 2] [5, 2, 0, 6, 4, 7, 1, 3] [5, 2, 0, 7, 3, 1, 6, 4] [5, 2, 0, 7, 4, 1, 3, 6] [5, 2, 4, 6, 0, 3, 1, 7] [5, 2, 4, 7, 0, 3, 1, 6] [5, 2, 6, 1, 3, 7, 0, 4] [5, 2, 6, 1, 7, 4, 0, 3] [5, 2, 6, 3, 0, 7, 1, 4] [5, 3, 0, 4, 7, 1, 6, 2] [5, 3, 1, 7, 4, 6, 0, 2] [5, 3, 6, 0, 2, 4, 1, 7] [5, 3, 6, 0, 7, 1, 4, 2] [5, 7, 1, 3, 0, 6, 4, 2] [6, 0, 2, 7, 5, 3, 1, 4] [6, 1, 3, 0, 7, 4, 2, 5] [6, 1, 5, 2, 0, 3, 7, 4] [6, 2, 0, 5, 7, 4, 1, 3] [6, 2, 7, 1, 4, 0, 5, 3] [6, 3, 1, 4, 7, 0, 2, 5] [6, 3, 1, 7, 5, 0, 2, 4] [6, 4, 2, 0, 5, 7, 1, 3] [7, 1, 3, 0, 6, 4, 2, 5] [7, 1, 4, 2, 0, 6, 3, 5] [7, 2, 0, 5, 1, 4, 6, 3] [7, 3, 0, 2, 5, 1, 6, 4] 总共有92中解法 检查方法调用次数:15720 ```