递归(迷宫回溯问题的实现)

场景分析

迷宫回溯问题:

 在上面这个迷宫中,小球需要绕过禁区(叉叉),从左上角走到右下角的出口,对于人来说可能一眼就能发现怎么走能到终点,但是对于计算机来说,它需要根据某种策略去进行尝试,最终才能抵达出口

基本介绍

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

递归回顾

其实我们在日常编码中已经用到过递归了,但是我们可能没有注意到,现在我们用下面几行简单的代码一起回顾一下:

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);
}

上面这个就是一个典型的递归,因为我们可以发现在test方法中,在if判断条件中,它对变量进行二次计算后再次调用自己这个方法本身

按照道理来说上面代码的打印顺序就应该是 4 3 2 1,但是我们来看它实际的打印:

我们可以发现,我们最开始传入的4却是最后打印的,这是为什么呢?

我们来看一下这个方法在底层JVM中的变化是什么样的:

流程分析

1.当程序执行到一个方法的时候,就会开辟一个独立的栈空间

2.在主方法里面调用test(4)的时候,在栈底就开辟了一个新的空间

3.但是在该方法中判断n>2,又调用了本方法test(3),那么这个时候test(4)的打印就不会再执行,就直接再开辟一个栈test(3);

4.以此,一直到开辟test(2)的时候,不再调用新的方法,直接执行打印

5.这个时候说明test(2)真正执行完了,test(2)这个栈也就消失了,然后再执行test(3)的打印,然后依次往下,最终到主方法的test(4)执行完毕则退出程序,最终输出顺序也就是:2 3 4

与普通循环的对比:

public static void main(String[] args) {
    test();
}
public static void test( ){
    int n = 5;
    for (int i = 0; i < n; i++) {
        if (n>=2){
            n--;
        }
            System.out.println("n="+n);
    }
}

上面就是普通的循环打印,那么它就是正常的顺序执行

 递归能解决的问题

1.各种数学问题,例如:八皇后问题、汉诺塔问题、阶乘问题、迷宫问题、球和篮子的问题;

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

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

递归规则

1.执行一个方法时,就创建一个新的受保护的独立空间(栈空间)

2.方法的局部变量是独立的,不会相互影响;如果方法中使用的是引用类型(比如数组)的变量,就会共享该引用类型的数据,因为该数据会在堆内存中被创建出来

3.递归必须向退出递归的条件逼近,否则就是无限递归出现StackOverflowError,栈内存溢出错误,死龟了

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

迷宫问题

 回到我们最开始的迷宫问题

要求

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

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

3.测试回溯现象;

流程分析

1.先创建一个二维数组用来模拟迷宫

2.将第一行和最后一行以及第一列和最后一列全部设置为禁区(设置成1),再设置个别的挡板禁区;

3.使用递归回溯来给小球找路;

4.从地图的(1,1)位置开始,如果能够走到map[6] [5]的位置,说明通路找到

5.当走到位置数据为0时,说明该点还没有走过,如果为1则表示禁区(不能走),如果为2则说明是通路可以走,如果为3表示该点已经走过,但是走不通,下一次回溯的时候就不再去走这条路了;

6.在走迷宫时,需要确定一个策略,我们假定一个策略是下->右->上->左,如果该点走不通再回溯;

代码实现

public static int[][] getLabyrinth(){

    //创建一个二维数组用来模拟迷宫
    int [][] map = new int[8][7];
    //将第一行和最后一行全部设为禁区;
    for (int i = 0; i < 7; i++) {
        map[0][i]=1;
        map[7][i]=1;
    }
    //将第一列和最后一列全部设为禁区;
    for (int i = 0; i < 8; i++) {
        map[i][0]=1;
        map[i][6]=1;
    }
    //设置个别禁区
    map[3][1]=1;
    map[3][2]=1;
    setWay(map, 1, 1);
   //输出新的地图(小球探测并标识后的地图):
    return map;
}
/**
 * @Author:Strine
 * @param map:地图
 * @param i :
 * @param j :从地图的第i行,第j列开始(1,1)
 * @return 如果找到通路则返回true,否则返回false,能到map[6][5]的位置,则说明通路找到
 * 约定:当map[i][j]为0时,说明该点还没有走过,当为1的时候表示禁区(不能走),如果为2则说明是通路可以走 ,如果为3表示该点已经走过但是走不通
 * */
public static boolean setWay(int [][]map,int i,int j){
    if (map[6][5]==2){  //说明通路已经找到了;
        return true;
    }else {
        if (map[i][j]==0){
            //先看当前这个点没有走过(为0),则按照策略走(下->右->上->左)
            //将该点设置为2,也就是假定该点是能走通的;
            map[i][j]=2;
            if (setWay(map,i+1,j)){       //如果按照这个点往下走,如果能走通则返回true,如果走不通则尝试向右走,
                return true;
            }else if (setWay(map,i,j+1)){
                return true;
            }else if (setWay(map, i-1, j)){
                return true;
            }else if (setWay(map, i, j-1)){
                return true;
            }else {
                map[i][j]=3; //==》直到尝试向左走,如果还是走不通,说明该店是走不通的,则将当前这个点重新设置为3
                return false;
            }
        }else {
            //那么如果当前这个点没有走过
            //如果当前这个点为1,说明为禁区不能走
            //如果当前点为2说明有人在走
            //如果当前点为3,说明有人走过了,因此直接返回false
            return false;
        }
    }
}

效果展示

流程简述

1.传入map[1] [1]

2.该位置为0,则将该位置设置为2,进行第一次递归调用,往下走;

3.下面这个位置依旧为0,因此将其设置为2,进行第二次递归调用,往下走

4.第二次递归调用发现下面这个位置为禁区,则返回false,回到第二次递归调用,进行往右走的递归调用(第三次);

5.发现右边位置为0,因此将其设置为2,进行第四次递归调用,往下走

6.发现下面的位置又是禁区,则返回false回到第四次递归调用,进行往右走的递归调用(第五次)

7.接下来就是一连串的四次递归调用(都是往下走成功了)

8.以此类推,后面几次调用都是发现下面的位置为禁区,然后回去一次递归往右走,最终走到出口返回true,然后从最后那个栈往前依次返回true,依次销毁

测试回溯现象

 我们在设置禁区的时候,再在右边加一个挡板,将它的路给堵死

1.我们发现刚开始map[1] [1]位置的时候,该位置设置为2,然后第一次递归调用往下走的时候也先设置为2;

2.但是下面这个位置进行下右上左的递归调用都为false,说明都走不通,则将当前位置设置为3,表示走不通,并返回false

3.因此发生了回溯,回到上一个位置map [1] [1],然后它就往右上左进行递归调用,发现也都为false,最终它也将自身修改为3,最终返回false

修改路径策略

将下->右->上->左的路径策略修改为上->右->下->左;

if (setWay(map,i-1,j)){      
    return true;
}else if (setWay(map,i,j+1)){
    return true;
}else if (setWay(map, i+1, j)){
    return true;
}else if (setWay(map, i, j-1)){
    return true;

其实也就是修改这几个递归调用的顺序;

效果展示

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是迷宫问题递归回溯算法Java实现: ```java import java.util.*; public class MazeSolver { // 迷宫的地图 private int[][] maze; // 迷宫的行数和列数 private int rows, cols; // 路径栈,用来保存从起点到终点的路径 private Stack<Point> path; public MazeSolver(int[][] maze) { this.maze = maze; this.rows = maze.length; this.cols = maze[0].length; this.path = new Stack<>(); } public void solve() { // 从起点开始搜索 Point start = new Point(0, 0); if (search(start)) { System.out.println("找到了一条从起点到终点的路径:"); for (Point p : path) { System.out.println("(" + p.x + ", " + p.y + ")"); } } else { System.out.println("没有找到从起点到终点的路径!"); } } // 搜索从当前位置开始的路径 private boolean search(Point cur) { // 如果当前位置是终点,则搜索成功 if (cur.x == rows - 1 && cur.y == cols - 1) { path.push(cur); return true; } // 如果当前位置不是终点,则尝试向四个方向搜索 if (maze[cur.x][cur.y] == 0) { // 如果当前位置是通路 maze[cur.x][cur.y] = -1; // 标记为已访问 path.push(cur); // 将当前位置加入路径 // 向右搜索 if (cur.y < cols - 1 && search(new Point(cur.x, cur.y + 1))) { return true; } // 向下搜索 if (cur.x < rows - 1 && search(new Point(cur.x + 1, cur.y))) { return true; } // 向左搜索 if (cur.y > 0 && search(new Point(cur.x, cur.y - 1))) { return true; } // 向上搜索 if (cur.x > 0 && search(new Point(cur.x - 1, cur.y))) { return true; } path.pop(); // 如果四个方向都搜索失败,则将当前位置从路径中删除 maze[cur.x][cur.y] = 0; // 标记为未访问 } return false; } public static void main(String[] args) { int[][] maze = { {0, 1, 0, 0, 0}, {0, 1, 0, 1, 0}, {0, 0, 0, 0, 0}, {0, 1, 1, 1, 0}, {0, 0, 0, 1, 0} }; MazeSolver solver = new MazeSolver(maze); solver.solve(); } } class Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } } ``` 这个程序使用了递归回溯算法来搜索迷宫中从起点到终点的路径。程序首先从起点开始搜索,如果当前位置是终点,则搜索成功;否则,程序尝试向四个方向(右、下、左、上)搜索。如果搜索成功,则返回 true;否则,程序将当前位置从路径中删除,并且将当前位置标记为未访问,然后返回 false。程序最后输出从起点到终点的路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Strine

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值