【Java数据结构和算法】010-递归:迷宫问题、八皇后问题(回溯算法)

目录

0、警醒自己

一、递归应用场景和调用机制

1、简介

2、两个案例

打印问题:

打印结果:

执行分析图:

阶乘问题:

运行结果:

阶乘逻辑解析:

二、递归能解决的问题和规则

1、额递能解决什么问题

2、递归调用规则

三、迷宫问题

1、问题描述

2、代码实现

3、运行结果

4、问题回顾

5、代码实现

6、运行结果

7、思考:最短路径问题

四、八皇后问题

1、问题描述

2、思路分析

说明:

3、代码实现

4、运行结果


0、警醒自己

1、学习不用心,骗人又骗己;

2、学习不刻苦,纸上画老虎;

3、学习不惜时,终得人耻笑;

4、学习不复习,不如不学习;

5、学习不休息,毁眼伤身体;

7、狗才等着别人喂,狼都是自己寻找食物;

 

一、递归应用场景和调用机制

1、简介

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

 

2、两个案例

打印问题:

package com.zb.ds;

//递归
public class Recursion {
    public static void main(String[] args) {
        test(4);
    }
    public static void test(int n) {
        if (n > 2) {
            test(n - 1);
        }
        System.out.println("n=" + n);
    }

}

打印结果:

n=2
n=3
n=4

执行分析图:

阶乘问题:

package com.zb.ds;

//递归
public class Recursion {
    public static void main(String[] args) {
        test(4);

        System.out.println(test1(5));
    }
    //打印问题
    public static void test(int n) {
        if (n > 2) {
            test(n - 1);
        }
        System.out.println("n=" + n);
    }
    //阶乘问题
    public static int test1(int n){
        if(n==1){
            return 1;
        }else {
            return test1(n-1) * n;
        }
    }
}

运行结果:

n=2
n=3
n=4
120

阶乘逻辑解析:

test1(4)*5 —— test1(3)*4*5 —— test1(2)*3*4*5 —— test1(1)*2*3*4*5 —— 1*2*3*4*5

 

二、递归能解决的问题和规则

1、额递能解决什么问题

各种数学问题如:8皇后问题、汉诺塔、阶乘问题、迷宫问题、球和篮子的问题(google编程大赛);

各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等;

将用栈解决的问题-->递归代码比较简洁;

 

2、递归调用规则

①当程序执行到一个方法时,就会开辟一个独立的空间(栈);

②每个空间的数据(局部变量)是相互独立的;

③如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据;

④递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError,死龟了。。。

⑤当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕;

 

三、迷宫问题

1、问题描述

①小球得到的路径,和程序员设置的找路策略有关即:找路的上下左右的顺序相关;

②再得到小球路径时,可以先使用(下右上左),再改成(上右下左),看看路径是不是有变化;

③测试回溯现象;

④思考: 如何求出最短路径? 

 

2、代码实现

package com.zb.ds;

//递归:迷宫问题
public class MiGong {
    public static void main(String[] args) {
        //创建一个二维数组,模拟迷宫
        //地图
        int[][] map = new int[8][7];
        //使用1表示墙
        //上下全部置为1
        for (int i = 0; i < 7; i++) {
            map[0][i] = 1;
            map[7][i] = 1;
        }
        //左右全部置为1
        for (int i = 0; i < 8; i++) {
            map[i][0] = 1;
            map[i][6] = 1;
        }
        //挡板
        map[3][1] = 1;
        map[3][2] = 1;
        //输出地图
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 7; j++) {
                System.out.print(map[i][j] +" ");
            }
            System.out.println();
        }
        //找路
        if(findWay(map, 1, 1)){
            System.out.println("找到路了!");
        }else {
            System.out.println("没找到路!");
        }
        //输出地图
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 7; j++) {
                System.out.print(map[i][j] +" ");
            }
            System.out.println();
        }
    }
    /**
     * 使用递归回溯,给小球找路
     * 说明:
     * 1、map是地图;
     * 2、i、j表示从哪个位置出发(1,1)
     * 3、如果小球能到map的(6,5),说明找到路
     * 4、约定:当地图map[i][j]为0时,表示未测试过(未走过),当为1时为墙,2为通路,3表示该位置已经走过但无法走通;
     * 5、确定一个走迷宫探路的策略:先走下边、下边走不通走右边,以此类推顺序为“下——右——上——左”,如果走不通再回溯
     * @param map 地图
     * @param i 从哪个位置开始找
     * @param j 从哪个位置开始找
     * @return  找到路返回true,否则返回false
     */
    public static boolean findWay(int[][] map,int i,int j){
        if(map[6][5]==2){
            return true;
        }else {
            if(map[i][j]==0){
                //没走过,按策略走:下——右——上——左
                map[i][j] = 2;//假定此点可以走通
                if(findWay(map,i+1,j)){
                    //向下走
                    return true;
                }else if(findWay(map,i,j+1)){
                    //向右走
                    return true;
                }else if(findWay(map,i-1,j)){
                    //向上走
                    return true;
                }else if(findWay(map,i,j-1)){
                    //向左走
                    return true;
                }else {
                    map[i][j] = 3;
                    return false;
                }
            }else{//如果map[i][j]!=0,可能是1,2,3
                return false;
            }
        }
    }
}

 

3、运行结果

(找到路了,2是路线,这个算法真是太强大了)

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 

 

4、问题回顾

我们可以知道,小球得到的路径和程序员设置的找路策略有很大关系,我们来讲原来的(下右上左)改成(上右下左),进行测试,看结果如何;

 

5、代码实现

(说明:只需要将i的+1和-1呼唤即可)

package com.zb.ds;

//递归:迷宫问题
public class MiGong {
    public static void main(String[] args) {
        //创建一个二维数组,模拟迷宫
        //地图
        int[][] map = new int[8][7];
        //使用1表示墙
        //上下全部置为1
        for (int i = 0; i < 7; i++) {
            map[0][i] = 1;
            map[7][i] = 1;
        }
        //左右全部置为1
        for (int i = 0; i < 8; i++) {
            map[i][0] = 1;
            map[i][6] = 1;
        }
        //挡板
        map[3][1] = 1;
        map[3][2] = 1;
        //输出地图
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 7; j++) {
                System.out.print(map[i][j] +" ");
            }
            System.out.println();
        }
        //找路
        if(findWay(map, 1, 1)){
            System.out.println("找到路了!");
        }else {
            System.out.println("没找到路!");
        }
        //输出地图
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 7; j++) {
                System.out.print(map[i][j] +" ");
            }
            System.out.println();
        }
    }
    /**
     * 使用递归回溯,给小球找路
     * 说明:
     * 1、map是地图;
     * 2、i、j表示从哪个位置出发(1,1)
     * 3、如果小球能到map的(6,5),说明找到路
     * 4、约定:当地图map[i][j]为0时,表示未测试过(未走过),当为1时为墙,2为通路,3表示该位置已经走过但无法走通;
     * 5、确定一个走迷宫探路的策略:先走下边、下边走不通走右边,以此类推顺序为“下——右——上——左”,如果走不通再回溯
     * @param map 地图
     * @param i 从哪个位置开始找
     * @param j 从哪个位置开始找
     * @return  找到路返回true,否则返回false
     */
    public static boolean findWay(int[][] map,int i,int j){
        if(map[6][5]==2){
            return true;
        }else {
            if(map[i][j]==0){
                //没走过,按策略走:下——右——上——左
                //更改策略为:上——右——下——左
                //说明:只需要将+1和-1呼唤即可
                map[i][j] = 2;//假定此点可以走通
                if(findWay(map,i-1,j)){
                    //向下走
                    return true;
                }else if(findWay(map,i,j+1)){
                    //向右走
                    return true;
                }else if(findWay(map,i+1,j)){
                    //向上走
                    return true;
                }else if(findWay(map,i,j-1)){
                    //向左走
                    return true;
                }else {
                    map[i][j] = 3;
                    return false;
                }
            }else{//如果map[i][j]!=0,可能是1,2,3
                return false;
            }
        }
    }
}

 

6、运行结果

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 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 

 

7、思考:最短路径问题

(搁置,我自己的想法还不够成熟,暂时不做实现!)

 

四、八皇后问题

1、问题描述

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

 

2、思路分析

①第一个皇后先放第一行第一列;

②第二个皇后放在第二行第一列、然后判断是否OK(是否冲突), 如果不OK,继续放在第二列、第三列、依次把所有列都放完,找到一个合适;

③继续第三个皇后,还是第一列、第二列……直到第8个皇后也能放在一个不冲突的位置,算是找到了一个正确解;

④当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解,全部得到;

⑤然后回头继续第一个皇后放第二列,后面继续循环执行①②③④的步骤;

 

说明:

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

 

3、代码实现

package com.zb.ds;

//递归:八皇后问题(回溯算法)
public class EightQueen {
    //定义一个max表示有多少个皇后
    int max = 8;
    int count = 0;
    //定义一个数组array,保存皇后放置位置的结果,比如:arr[8] = {0 , 4, 7, 5, 2, 6, 1, 3}
    int[] array = new int[max];
    public static void main(String[] args) {
        //测试八皇后
        new EightQueen().check(0);
    }

    //编写一个方法,放置第n个皇后
    //每一次递归都有一套for循环,所有也有了从后往前回溯,成立的话摆下一个皇后,不成立的话将当前皇后摆到下一列继续判断,
    // 假如当前行都不成立,回到上一行进行如此循环的判断,直到全部走通,简直amazing
    private void check(int n){
        if (n==max){//n==8,已经放了8个皇后
            print();
            return;
        }
        //依次放入皇后,并判断是否冲突
        for (int i = 0; i < max; i++) {
            //先把当前皇后n,放到改行的第1列
            array[n] = i;
            //判断当放置第n个皇后到i列时,是否冲突
            if(judge(n)){//不冲突
                //接着放n+1和皇后,开始递归
                check(n+1);
            }
            //如果冲突就继续执行循环,放到下一列
        }
    }

    //当我们放置第n个皇后去检测是否与前面摆放的皇后有冲突
    //n表示第n个皇后,i表示n前面的某个皇后,array[n]表示第n个皇后的位置,array[i]表示n前面的某个皇后的位置
    private boolean judge(int n){
        for (int i = 0; i < n; i++) {
            if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i])){
                //在同一列或者在同一斜线上
                //在同一斜线上:Math.abs(n-i)表示相差的列数,Math.abs(array[n] - array[i])表示相差的行数,如果二者相等,说明在一条斜线上
                return false;
            }
        }
        return true;
    }

    //写一个方法,可以讲皇后摆放的位置打印出来
    private void print(){
        count++;
        System.out.print("第" + count + "种解法:");
        for (int value : array) {
            System.out.print(value + " ");
        }
        System.out.println();
    }
}

 

4、运行结果

第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 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值