递归——迷宫回溯问题、八皇后问题(Java)

1.8 递归

1.8.1 了解递归

  • 递归在程序语言中简单的理解是:方法自己调用自己

  • 递归需要遵守的规则
    在这里插入图片描述
    递归调用机制

  • 递归每次执行(调用)一次方法时,会在内存开辟一个栈空间

  • 递归方法从哪里开始进入递归,返回的时候就从哪里返回跳出

  • 可以看下图方便理解:
    在这里插入图片描述

  • 递归简单案例了解
    打印阶乘

//用递归方法求阶乘
public class Factorial{
    public static void main(String[] args){
        int N = 5;
        for(int n = 0; n <= N; n++){
            int fact = factorial(n);
            System.out.println(n + " 的阶乘值是: " + fact);
        }               
    }
    public static int factorial(int n){
        if(n < 1)
            return 1;       
        return factorial(n-1) * n;
    }
}

输出:

0 的阶乘值是: 1
1 的阶乘值是: 1
2 的阶乘值是: 2
3 的阶乘值是: 6
4 的阶乘值是: 24
5 的阶乘值是: 120

1.8.2 递归解决迷宫回溯问题

需求

迷宫回溯问题需求
在这里插入图片描述
思路

* 1. 初始化地图map,设置墙壁和一些障碍物,定义终点的坐标endX,endY
* 2. 从初始点(startX,startY)开始递归回溯探索路径
* 说明: 1.当map[i][j]=0 表示小球这个点还没走到,
*                   =1 表示墙
*                   =2 表示格子走过,但是我们不能再次走了
*                   =3 表示该点已经尝试走过,但是发现无法走通
*      2.走迷宫的策略 下startX+1 右startY+1 上startX-1 左startY-1  (注意XY和我们日常使用的XY坐标有点不一样)
*      3.从最开始格子开始,按照策略(下右上左)行走,
*          遇到目标格子(终点),直接返回true
*          遇到已知的无法通过格子(map[i][j] != 0)返回
*          遇到未知格子(map[i][j]=0)尝试探索将该格子的四个方向,只要该格子四个方向有一个可以通过返回true。否则将该格子设置为无法通过map[i][j]=3
*      4. 不同的行走策略都会导致最终寻找到的路径的不同,现在行走策略找到的路径不一定是最短路径

实现代码与输出结果:

public class mazeOut {
    private static int [][]  map = null;
    private static int i;
    private  static int j;
    private static int endX;
    private static int endY;
    public static void initMap(int i1, int j1,int x,int y){ // 初始化迷宫地图
        i = i1;
        j = j1;
        endX = x;
        endY = y;
        map = new int[i][j];
        // 设置墙壁
        for(int j=0;j<j1;j++){
            map[0][j] = 1;
            map[i1-1][j] = 1;
        }
        for(int i=0;i<i1;i++){
            map[i][0] = 1;
            map[i][j1-1] = 1;
        }
    }
    public static void showMap(){ // 输出迷宫地图
        for(int i1=0;i1<i;i1++){
            for (int j1=0;j1<j;j1++){
                System.out.print(map[i1][j1]+" ");
            }
            System.out.println();
        }
    }

    /**
     *
     * @param startX 开始探索格子的X坐标
     * @param startY 开始探索格子的Y坐标
     * @return 返回该点是否可通
     * 说明: 1.当map[i][j]=0 表示小球这个点还没走到,
     *                   =1 表示墙
     *                   =2 表示格子走过,但是我们不能再次走了
     *                   =3 表示该点已经尝试走过,但是发现无法走通
     *      2.走迷宫的策略 下i+1 右j+1 上i-1 左j-1
     *      3.从最开始格子开始,按照策略(下右上左)行走,
     *          遇到目标格子(终点),直接返回true
     *          遇到已知的无法通过格子(map[i][j] != 0)返回
     *          遇到未知格子(map[i][j]=0)尝试探索将该格子的四个方向,只要该格子四个方向有一个可以通过返回true。否则将该格子设置为无法通过map[i][j]=3
     *      4. 不同的行走策略都会导致最终寻找到的路径的不同,现在行走策略找到的路径不一定是最短路径
     */
    public static boolean findWay(int startX,int startY){
        // 1. 判断终点是否走通过
        if(map[endX][endY] == 2)
            return true;
        // 2.如果现所处格子未探索过(map[startX][startY]==0),开始按照策略行走
        if(map[startX][startY] == 0){
            // 暂且将当前格子设置为2
            map[startX][startY] = 2;
            if(findWay(startX+1,startY)) // 向下
                return true;
            else if (findWay(startX,startY+1)) // 向右
                return true;
            else if(findWay(startX-1,startY))  // 向上
                return true;
            else if(findWay(startX,startY-1))  // 向左
                return true;
            else { // 如果四个方向都是死路,将现在格子标记为map[][] = 3
                map[startX][startY] = 3;
                return false;
            }
        }else // 如果当前格子是已知格子map[][] = 1,2,3  直接返回false
            return false;

    }
    public static void main(String[] args) {
        initMap(7,8,5,4);
        // 手动添加障碍物
        map[2][3] = 1;
        map[3][2] = 1;
        System.out.println("迷宫初始地图: ");
        showMap();
        findWay(2,2);
        System.out.println("寻找后的迷宫地图: ");
        showMap();
    }
}

输出结果:

迷宫初始地图: 
1 1 1 1 1 1 1 1 
1 0 0 0 0 0 0 1 
1 0 0 1 0 0 0 1 
1 0 1 0 0 0 0 1 
1 0 0 0 0 0 0 1 
1 0 0 0 0 0 0 1 
1 1 1 1 1 1 1 1 
寻找后的迷宫地图: 
1 1 1 1 1 1 1 1 
1 0 2 2 2 0 0 1 
1 0 2 1 2 0 0 1 
1 0 1 0 2 0 0 1 
1 0 0 0 2 0 0 1 
1 0 0 0 2 0 0 1 
1 1 1 1 1 1 1 1 

1.8.3 递归解决八皇后问题

问题介绍
在这里插入图片描述
思路分析
在这里插入图片描述
借用代码随想录的一张图解来简要说明递归的过程
在这里插入图片描述

  • 定义一个max 定义有多少个皇后 默认8
  • 定义一维数组array 保存皇后放置位置的结果
  • 写一个方法 打印皇后摆放的位置
  • 写一个方法 判断当前皇后n摆放位置是否会和前面摆放皇后冲突同一列 同一条斜线
    • 同一列 array[i] = array[n]
    • 同一条斜线 abs(n-i) = abs(array[n]-array[i])
    • 因为在放置皇后的时候n是不断递增的,所以n根本不可能会相同,所以不可能会出现同一行的情况
  • 写一个方法 放置第n个皇后(开始递归
    • 摆放一个皇后n,无冲突,开始摆放下一个皇后n+1有冲突修改摆放列的位置array[n]直到无冲突
    • 如果无论怎么修改摆放列都会冲突回溯到上一个皇后n-1,修改上一个皇后的摆放位置array[n-1]重新开始下一个皇后n的摆放
    • 直到n==max (代表前0—max-1的皇后都摆放完毕,即全部皇后摆放完毕,开始输出摆放位置,结束本次递归

实现代码与输出结果

代码

public class QueenN { // N皇后问题
    private int max;  // N 表示皇后个数
    private int[] array;  // 记录N皇后摆放位置
    private int count; // 记录N皇后有几种解法
    public boolean NoClash(int n){ // 判断摆放第n个皇后的时候是否会与之前皇后的摆放位置发生冲突
        /**
         * array[n] == array[i] 判断是否同列
         * Math.abs(n-i) == Math.abs(array[n]-array[i]) 判断是否同斜线(Math.abs解决正负斜线问题)
         */
        for(int i=0;i<n;i++){
            if (array[n] == array[i] || Math.abs(n-i) == Math.abs(array[n]-array[i]))
                return false;
        }
        // 无冲突 返回true
        return true;
    }
    public void init(int max){
        this.max = max;
        array = new int[max];
    }
    public void show(){ // 输出一个解法N皇后的摆放位置
        count++;
        System.out.print("["+count+"]: ");
        for(int i=0;i<array.length;i++){
            System.out.print(array[i]+" ");
        }
        System.out.println();
    }
    public void putChess(int n){ // n为当前皇后标号
        // n = max时,所有皇后都摆放完毕 结束递归
        if(n == max){ // 当n=max的时候,会return到上一层(上一行)
            show();
            return;
        }
        //
        for(int i=0;i<max;i++){
            // 先摆放皇后 才能开始判断是否冲突
            array[n] = i;
//            System.out.println(array[n]);
            if(NoClash(n)){ // 如果摆放冲突,会自动开始下一列的皇后摆放,直到所有列都尝试过或者摆放成功
                putChess(n+1); // 如果不冲突,开始下一个皇后的摆放
            }
        }
        // 若所有列摆放皇后都不合适,会自动return回溯到上一个皇后,调整摆放位置后再次开始下一层皇后的摆放
    }
    // 测试使用八皇后 有92种解法
    public static void main(String[] args) {
        QueenN queen8 = new QueenN();
        queen8.init(8); // 修改数字即可更改为N皇后问题
//        queen8.show();
        queen8.putChess(0);
        System.out.println("总计解法: "+queen8.count);
    }
}

输出结果

[1]: 0 4 7 5 2 6 1 3 
[2]: 0 5 7 2 6 3 1 4 
[3]: 0 6 3 5 7 1 4 2 
[4]: 0 6 4 7 1 3 5 2 
[5]: 1 3 5 7 2 0 6 4 
[6]: 1 4 6 0 2 7 5 3 
[7]: 1 4 6 3 0 7 5 2 
[8]: 1 5 0 6 3 7 2 4 
[9]: 1 5 7 2 0 3 6 4 
[10]: 1 6 2 5 7 4 0 3 
[11]: 1 6 4 7 0 3 5 2 
[12]: 1 7 5 0 2 4 6 3 
[13]: 2 0 6 4 7 1 3 5 
[14]: 2 4 1 7 0 6 3 5 
[15]: 2 4 1 7 5 3 6 0 
[16]: 2 4 6 0 3 1 7 5 
[17]: 2 4 7 3 0 6 1 5 
[18]: 2 5 1 4 7 0 6 3 
[19]: 2 5 1 6 0 3 7 4 
[20]: 2 5 1 6 4 0 7 3 
[21]: 2 5 3 0 7 4 6 1 
[22]: 2 5 3 1 7 4 6 0 
[23]: 2 5 7 0 3 6 4 1 
[24]: 2 5 7 0 4 6 1 3 
[25]: 2 5 7 1 3 0 6 4 
[26]: 2 6 1 7 4 0 3 5 
[27]: 2 6 1 7 5 3 0 4 
[28]: 2 7 3 6 0 5 1 4 
[29]: 3 0 4 7 1 6 2 5 
[30]: 3 0 4 7 5 2 6 1 
[31]: 3 1 4 7 5 0 2 6 
[32]: 3 1 6 2 5 7 0 4 
[33]: 3 1 6 2 5 7 4 0 
[34]: 3 1 6 4 0 7 5 2 
[35]: 3 1 7 4 6 0 2 5 
[36]: 3 1 7 5 0 2 4 6 
[37]: 3 5 0 4 1 7 2 6 
[38]: 3 5 7 1 6 0 2 4 
[39]: 3 5 7 2 0 6 4 1 
[40]: 3 6 0 7 4 1 5 2 
[41]: 3 6 2 7 1 4 0 5 
[42]: 3 6 4 1 5 0 2 7 
[43]: 3 6 4 2 0 5 7 1 
[44]: 3 7 0 2 5 1 6 4 
[45]: 3 7 0 4 6 1 5 2 
[46]: 3 7 4 2 0 6 1 5 
[47]: 4 0 3 5 7 1 6 2 
[48]: 4 0 7 3 1 6 2 5 
[49]: 4 0 7 5 2 6 1 3 
[50]: 4 1 3 5 7 2 0 6 
[51]: 4 1 3 6 2 7 5 0 
[52]: 4 1 5 0 6 3 7 2 
[53]: 4 1 7 0 3 6 2 5 
[54]: 4 2 0 5 7 1 3 6 
[55]: 4 2 0 6 1 7 5 3 
[56]: 4 2 7 3 6 0 5 1 
[57]: 4 6 0 2 7 5 3 1 
[58]: 4 6 0 3 1 7 5 2 
[59]: 4 6 1 3 7 0 2 5 
[60]: 4 6 1 5 2 0 3 7 
[61]: 4 6 1 5 2 0 7 3 
[62]: 4 6 3 0 2 7 5 1 
[63]: 4 7 3 0 2 5 1 6 
[64]: 4 7 3 0 6 1 5 2 
[65]: 5 0 4 1 7 2 6 3 
[66]: 5 1 6 0 2 4 7 3 
[67]: 5 1 6 0 3 7 4 2 
[68]: 5 2 0 6 4 7 1 3 
[69]: 5 2 0 7 3 1 6 4 
[70]: 5 2 0 7 4 1 3 6 
[71]: 5 2 4 6 0 3 1 7 
[72]: 5 2 4 7 0 3 1 6 
[73]: 5 2 6 1 3 7 0 4 
[74]: 5 2 6 1 7 4 0 3 
[75]: 5 2 6 3 0 7 1 4 
[76]: 5 3 0 4 7 1 6 2 
[77]: 5 3 1 7 4 6 0 2 
[78]: 5 3 6 0 2 4 1 7 
[79]: 5 3 6 0 7 1 4 2 
[80]: 5 7 1 3 0 6 4 2 
[81]: 6 0 2 7 5 3 1 4 
[82]: 6 1 3 0 7 4 2 5 
[83]: 6 1 5 2 0 3 7 4 
[84]: 6 2 0 5 7 4 1 3 
[85]: 6 2 7 1 4 0 5 3 
[86]: 6 3 1 4 7 0 2 5 
[87]: 6 3 1 7 5 0 2 4 
[88]: 6 4 2 0 5 7 1 3 
[89]: 7 1 3 0 6 4 2 5 
[90]: 7 1 4 2 0 6 3 5 
[91]: 7 2 0 5 1 4 6 3 
[92]: 7 3 0 2 5 1 6 4 
总计解法: 92
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值