2021-10-21 四、递归

1. 递归是什么

递归就是方法自己调用自己,每次调用时传入不同的变量。
递归有助于编程者解决复杂的问题,同时可以让代码变得简洁。

1.1 递归的运行说明

在这里插入图片描述
根据JVM的运行规则:

  1. 当程序执行到一个方法时,就会在栈空间中开辟一块新的栈帧空间;
  2. 每个空间中都是互相独立的,也就是说数据(局部变量)不会互通

在这里插入图片描述
运行解释:

  1. 当进入到main方法时,会在栈中开辟一块main方法栈帧,然后依次执行语句,
  2. 执行到test(4)方法时,会进入到test方法体,从而开辟新的栈帧…然后以此类推
  3. 当方法运行完之后,栈中的内存空间会自动回收,从图中可以看到,最先回收的是n=2的栈帧,然后n=3…最后回到main方法
  4. 所以,该程序的输出顺序为n=2,n=3,n=4
    在这里插入图片描述

1.2 递归的应用场景

  1. 各种数学问题如: 8皇后问题 , 汉诺塔, 阶乘问题, 迷宫问题, 球和篮子的问题(google编程大赛)
  2. 各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等.
  3. 将用栈解决的问题–>递归代码比较简洁

1.3 递归必须遵守的规则

1、每执行一个方法,就创建一个新的受保护的独立空间(栈空间) — 默认遵守了(main)
2、方法的局部变量是独立的,不会相互影响, 比如n变量
3、如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据。(因为引用类型变量存放在堆中,堆是共享内存空间)
4、递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError,栈溢出)
5、当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕

2. 递归回溯算法(迷宫问题)

根据递归的实际应用场景,这里选用迷宫问题来描述递归回溯算法

2.1 设计思路

  1. 使用二维数组来模拟迷宫,并手动加上挡板(围墙用1表示,可走空间用0表示,能走通的路线用2表示,走过但是无法通行的路线用3表示)
  2. 指定起点和终点,判断走到终点时跳出递归
  3. 设置行走规则:下=>右=>上=>左

2.2 代码实现

生成迷宫

    private static int[][] createMiGong() {
        int[][] miGong = new int[8][7];
        for (int i = 0; i < miGong.length; i++) {
            for (int j = 0; j < miGong[i].length; j++) {
                miGong[0][j]
                        = miGong[miGong.length - 1][j]
                        = miGong[i][0]
                        = miGong[i][miGong[i].length - 1]
                        = miGong[3][1]
                        = miGong[3][2] = 1;
                System.out.print(miGong[i][j] + "\t");
            }
            System.out.println();
        }
        return miGong;
    }
1	1	1	1	1	1	1	
1	0	0	0	0	0	1	
1	0	0	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

走迷宫

    /**
     * 走迷宫
     */
    public static boolean miGongHuiSu(int[][] mig, int i, int j) {
        //到达出口直接返回true,只要返回值为true就开始回溯了
        if (mig[6][5] == 2) {
            return true;
        } else {
            //还没走过
            if (mig[i][j] == 0) {
                //假设走通了
                mig[i][j] = 2;
                //往下走如果到达终点,那么就返回true
                if (miGongHuiSu(mig, i + 1, j)) {
                    return true;
                } else if (miGongHuiSu(mig, i, j + 1)) {
                    //往右走
                    return true;
                } else if (miGongHuiSu(mig, i - 1, j)) {
                    //往上走
                    return true;
                } else if (miGongHuiSu(mig, i, j - 1)) {
                    //往左走
                    return true;
                } else {
                    //上下左右都走不通走不通
                    mig[i][j] = 3;
                    return false;
                }
            } else {
                //当mig[i][j]!=0时就返回false,说明当前的位置要么是墙,要么走过了,要么走不通
                return false;
            }
        }
    }

2.3 完整代码

public class Demo14 {
    public static void main(String[] args) {

        //8行7列迷宫  加上挡板 ,1为挡板
        int[][] miGong = createMiGong();

        System.out.println("开始走迷宫啦~~");
        miGongHuiSu(miGong, 1, 1);
        show(miGong);
    }

    /**
     * 生成迷宫
     * @return
     */
    private static int[][] createMiGong() {
        int[][] miGong = new int[8][7];
        for (int i = 0; i < miGong.length; i++) {
            for (int j = 0; j < miGong[i].length; j++) {
                miGong[0][j]
                        = miGong[miGong.length - 1][j]
                        = miGong[i][0]
                        = miGong[i][miGong[i].length - 1]
                        = miGong[3][1]
                        = miGong[3][2] = 1;
                System.out.print(miGong[i][j] + "\t");
            }
            System.out.println();
        }
        return miGong;
    }

    /**
     * 展示迷宫
     */
    public static void show(int[][] mig) {
        for (int[] ints : mig) {
            for (int anInt : ints) {
                System.out.print(anInt + "\t");
            }
            System.out.println();
        }
    }

    /**
     * 走迷宫
     */
    public static boolean miGongHuiSu(int[][] mig, int i, int j) {
        //到达出口直接返回true,只要返回值为true就开始回溯了
        if (mig[6][5] == 2) {
            return true;
        } else {
            //还没走过
            if (mig[i][j] == 0) {
                //假设走通了
                mig[i][j] = 2;
                //往下走
                if (miGongHuiSu(mig, i + 1, j)) {
                    // 如果能到达终点,那么就返回true,否则会因为返回的是false到下面的else if当中
                    return true;
                } else if (miGongHuiSu(mig, i, j + 1)) {
                    //往右走
                    return true;
                } else if (miGongHuiSu(mig, i - 1, j)) {
                    //往上走
                    return true;
                } else if (miGongHuiSu(mig, i, j - 1)) {
                    //往左走
                    return true;
                } else {
                    //上下左右都走不通走不通
                    mig[i][j] = 3;
                    return false;
                }
            } else {
                //当mig[i][j]!=0时就返回false,说明当前的位置要么是墙,要么走过了,要么走不通
                return false;
            }
        }
    }
}

输出结果 如果不理解建议多debug看

1	1	1	1	1	1	1	
1	0	0	0	0	0	1	
1	0	0	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	0	0	0	0	1	
1	2	2	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	

2.4 修改迷宫行走策略

上=>右=>下=>左
这段代码贴到完整代码里面并加上相关调用即可使用

   /**
     * 修改迷宫行走策略: 上-》右-》下-》左
     * 并且加上步长计算,可以用来比较最短路径,
     */
    public static boolean miGongHuiSu2(int[][] mig, int i, int j, int step) {
        //到达出口
        if (mig[6][5] == 2) {
            System.out.println(step);
            return true;
        } else {
            //还没走过
            if (mig[i][j] == 0) {
                step++;
                //假设走通了
                mig[i][j] = 2;
                //往上走
                if (miGongHuiSu2(mig, i - 1, j, step)) {
                    return true;
                } else if (miGongHuiSu2(mig, i, j + 1, step)) {
                    //往右走
                    return true;
                } else if (miGongHuiSu2(mig, i + 1, j, step)) {
                    //往下走
                    return true;
                } else if (miGongHuiSu2(mig, i, j - 1, step)) {
                    //往左走
                    return true;
                } else {
                    step--;
                    //上下左右都走不通走不通
                    mig[i][j] = 3;
                    return false;
                }
            } else {
                step--;
                //mig[i][j]!=0,eg:1,2,3
                return false;
            }
        }
    }

输出结果

修改策略之后走迷宫~~
10 // 步长
1	1	1	1	1	1	1	
1	2	2	2	2	2	1	
1	0	0	0	0	2	1	
1	1	1	0	0	2	1	
1	0	0	0	0	2	1	
1	0	0	0	0	2	1	
1	0	0	0	0	2	1	
1	1	1	1	1	1	1	

3. 八皇后问题

3.1 八皇后问题介绍

1、八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,
2、人话:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
在这里插入图片描述

3.2 设计思路

操作步骤:

  1. 第一个皇后先放第一行第一列
  2. 第二个皇后放在第二行第一列、然后判断是否OK[是否冲突], 如果不OK,继续放在第二列、第三列… 直到找到一个合适的位置
  3. 继续第三个皇后,还是第一列、第二列……直到第8个皇后也能放在一个不冲突的位置(在这个过程中可能发生回退到步骤二调整之前放好的皇后),等开始找第九个皇后的时候算找到了一个正确解
  4. 当得到一个正确解时开始回溯,栈会回退到上一个栈,后移上一个栈中皇后的位置,甚至会回退多个栈,调整多个皇后的位置,就是按照步骤2-3的思路进行递归回溯。
  5. 反复1-4步骤就能得到所有的解

说明:理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法,用一个一维数组即可解决问题. arr[8] = {0 , 4, 7, 5, 2, 6, 1, 3}
arr[i]:表示第i+1行,即第i+1个皇后;
arr[i] = val:表示第i+1个皇后,放在第i+1行的第val+1列

3.3 代码实现

public class Demo15 {
    private final static Integer MAX = 8;
    /**
     * 这里使用一维数组来存放解法
     */
    private final static Integer[] ARRAY = new Integer[MAX];
    /**
     * 记录解法个数
     */
    private static Integer count = 0;
    /**
     * 记录判断次数
     */
    private static Integer bif = 0;

    public static void main(String[] args) {
        check(0);
        System.out.printf("一共有 %d 种解法\n", count);
        System.out.printf("一共判断了%d次", bif);
    }

    /**
     * 判断皇后是否冲突
     * 3.不用判断是否同一行,因为在设计的时候,通过数组角标代表行,N每次都在递增(不会出现在同一行的结果)
     *
     * @param n 第几个皇后,或者是第几行皇后。
     */
    private static boolean judge(int n) {
        bif++;
        for (int i = 0; i < n; i++) {
            /*
             * 1、ARRAY[i].equals(ARRAY[n]) 表示判断 第n个皇后是否和前面的n-1个皇后在同一列
             * 2、Math.abs(n-i) == Math.abs(ARRAY[n] - ARRAY[i])
             * 表示判断第n个皇后是否和第i皇后是否在同一斜线(也就是求斜率),
             * 因为Math.abs()求的是绝对值,所以只需要考虑(y2-y1)/(x2-x1) = 1这种情况
             */
            if (ARRAY[i].equals(ARRAY[n]) || Math.abs(n - i) == Math.abs(ARRAY[n] - ARRAY[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * 放置第n个皇后
     */
    private static void check(int n) {
        // 表示判断到第九个皇后,说明八皇后都没有冲突,可以直接打印结果了
        if (n == MAX) {
            print();
            return;
        }
        /*
         * 每次调用check函数都从第一列开始放置,ARRAY[n]代表第n+1行
         */
        for (int i = 0; i < MAX; i++) {
            ARRAY[n] = i;
            // 这里说明没冲突,可以递归调用函数来判断下一个皇后的位置
            if (judge(n)) {
                check(n + 1);
            }
            // 有冲突就会i++进入for的下一个循环,这里实现了回溯
        }
    }

    /**
     * 输出皇后的摆放结果
     */
    private static void print() {
        count++;
        for (Integer integer : ARRAY) {
            System.out.print(integer + " ");
        }
        System.out.println();
    }
}
上一篇总目录下一篇
三、栈数据结构篇(Java)目录五、排序(上:冒泡、简单选择)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值