递归应用场景
递归的概念
简单的说: 递归就是方法自己调用自己,每次调用时,传入不同的变量,递归有助于编程者解决复杂的问题,同时可以让代码变得简洁
递归调用机制
列举两个小案例,来帮助大家理解递归,如果学习过了,就回顾一下,没学习过,就好好看
- 打印问题
 - 阶乘问题
 
案例
package com.dance.recursion;public class Demo {public static void main(String[] args) {test(3);// 阶乘案例 1 x 2 x 3 = 6System.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 ```
