递归-迷宫问题(回溯)

递归

在定义一个函数时,出现直接或间接调用自己的成分,称之为递归

递归算法解决问题的特点:

1 .递归就是方法里调用自身。
2. 在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。
3. 递归有助于编程者解决复杂的问题(如迷宫问题,汉诺塔等),同时可以让代码变得简洁

递归需要遵守的重要规则

  1. 执行一个方法时,就是创建一个新的受保护的独立空间(栈空间)
  2. 方法的局部变量是独立的,不会相互影响,比如N变量
  3. 如果方法中使用的是引用类型变量(如数组),就会共享该应用类型的数据
  4. 递归必须向退出递归的条件逼近,不然就回出现死递归,出现StackOverflowError。
  5. 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就是执行完毕。

迷宫问题

在这里插入图片描述
求迷宫问题就是求出从入口到出口的路径,在求解时,通常用的是“穷举求解”的方法,即从入口出发,顺着某一个方向向前试探,若能呢走通,则继续往前走,否则沿着原路退回,换一个方向在继续试探,直到所有可能的通的路都试探完为止。为了保证在任何位置上都能沿着原路退回(回溯),需要用一个后进先出的栈来保存从入口到当前位置的路径。

如图:
对应图中的每个方块,用空白表示 通道,用阴影表示墙。

下面我们用递归来实现迷宫问题
为了表示迷宫,我们可以使用一个二维数组来表示迷宫,图中阴影部分表示墙,我们可以把他们初始化为1表示墙
空白位置为0,表示走得通。

  1. 定义一个方法返回迷宫:
  2. 定义方法走迷宫
  3. 传入保存路径的栈和初始路径开始走迷宫
 public class Labyrinth {

    public static void main(String[] args) {
        int[][] newlabyrinth = newlabyrinth();
        // 栈用来保存走过路的即保存数组的下标,i,j
        Stack<Integer> stack = new Stack();
        goLabyrinth(newlabyrinth, stack,1, 1);

        // 遍历二维数组,看路径
        // 输出遍历看看效果
        for (int[] ints : newlabyrinth) {
            for (int anInt : ints) {
                System.out.print(anInt+ "  ");
            }
            System.out.println();
        }

    }

    /**
     * 初始化迷宫
     * 初始化为1表示墙
     * 空白位置为0
     * @return
     */
    public static int[][] newlabyrinth() {
        int[][] labyrinth = new int[10][10];
        for (int i = 0; i < labyrinth.length; i++) {
            for (int j = 0; j < labyrinth[i].length; j++) {
                // 第一行,和最后一行是墙
                labyrinth[0][j] = 1;
                labyrinth[9][j] = 1;
                // 第一列和最后一列是墙
                labyrinth[i][0] = 1;
                labyrinth[i][9] = 1;
                // 图中其余位置的阴影位置
                labyrinth[1][3] = 1;
                labyrinth[1][7] = 1;
                labyrinth[2][3] = 1;
                labyrinth[2][7] = 1;
                labyrinth[3][5] = 1;
                labyrinth[3][6] = 1;
                labyrinth[4][2] = 1;
                labyrinth[4][3] = 1;
                labyrinth[4][4] = 1;
                labyrinth[5][4] = 1;
                labyrinth[6][2] = 1;
                labyrinth[6][6] = 1;
                labyrinth[7][2] = 1;
                labyrinth[7][3] = 1;
                labyrinth[7][4] = 1;
                labyrinth[7][6] = 1;
                labyrinth[7][7] = 1;
                labyrinth[8][1] = 1;
            }
        }

//        // 输出遍历看看效果
//        for (int[] ints : labyrinth) {
//            for (int anInt : ints) {
//                System.out.print(anInt+ "  ");
//            }
//            System.out.println();
//        }

        return labyrinth;
    }

    /**
     * 1.我们从入口labyrinth[1][1]开始进入,找到出口位置labyrinth[8][8]
     * 2.根据规则,我们只能走空白位置,遇到墙,换个方向。
     * 3.我们定义一个走路的规则,先往下,在往右,上,左
     * 4.对于走过的路我们可以初始化为2
     *
     * i,j表示位置当前所处在的位置
     *
     */
    public static boolean goLabyrinth(int[][] labyrinth, Stack<Integer> stack, int i, int j) {
        if (i < 1 || j < 1) {
            throw new RuntimeException("初始位置不对");
        }

        // 出口是 labyrinth[8][8]

       if (i ==8 && j == 8) {
           labyrinth[8][8] = 2;
           return true;
       }

       if (labyrinth[i][j] == 0) {
           // 走的通的路设置为2
           labyrinth[i][j] = 2;
           // 保存走得通的路径,j保存在i的下面,取得时候先弹出的是i
           stack.push(j);
           stack.push(i);
           // 先往下,在往右,上,左
           if (labyrinth[i+1][j] == 0) {
               goLabyrinth(labyrinth,stack,i +1,j);
           } else if (labyrinth[i][j+1] == 0) {
               goLabyrinth(labyrinth,stack,i,j+1);
           } else if (labyrinth[i-1][j] == 0) {
               goLabyrinth(labyrinth,stack,i-1,j);
           } else if (labyrinth[i][j-1] == 0) {
               goLabyrinth(labyrinth,stack,i,j-1);
           } else {
               // 走到这说明都走不通是死路,我们设置死路为3,需要原路退回,即回溯
               labyrinth[i][j] = 3;
               // 弹出刚才保存在栈的此路坐标,此路不同不保存
               stack.pop();
               stack.pop();
               if (stack.empty()) {
                   return false;
               }
               // 退回到上一格;取出栈顶的元素 i 和 j
               Integer popI = stack.pop();
               Integer popJ = stack.pop();
               // 把上一格设置为没有走过
               labyrinth[popI][popJ] = 0;
               goLabyrinth(labyrinth,stack, popI, popJ);

           }
       }

        return false;
    }

输出结果迷宫图:

初始化的迷宫:
1  1  1  1  1  1  1  1  1  1  
1  0  0  1  0  0  0  1  0  1  
1  0  0  1  0  0  0  1  0  1  
1  0  0  0  0  1  1  0  0  1  
1  0  1  1  1  0  0  0  0  1  
1  0  0  0  1  0  0  0  0  1  
1  0  1  0  0  0  1  0  0  1  
1  0  1  1  1  0  1  1  0  1  
1  1  0  0  0  0  0  0  0  1  
1  1  1  1  1  1  1  1  1  1  
开始走迷宫:
1  1  1  1  1  1  1  1  1  1  
1  2  0  1  0  0  0  1  0  1  
1  2  0  1  0  0  0  1  0  1  
1  2  0  0  0  1  1  0  0  1  
1  2  1  1  1  0  0  0  0  1  
1  2  2  2  1  0  0  0  0  1  
1  3  1  2  2  2  1  0  0  1  
1  3  1  1  1  2  1  1  0  1  
1  1  0  0  0  2  2  2  2  1  
1  1  1  1  1  1  1  1  1  1  

从输出结果我们可以看出,2是走迷宫的路径,其实我们可以有很多种走法,需要我们自己定义规则,每个规则的走法都有可能不一样。

我们来想一下如何求出最短路劲问题:
其实求出最短路径也简单,上面的代码我们已经把走过的路径保存在栈中了,我们只需要把走的规则给穷举一遍,保存每一种规则走的路径长度,然后比较哪一种最短即可。大家有兴趣可以自己完成

  • 心得总结
    其实像这种数据结构和算法题需要自己手动去写一遍,最好按照自己的思路把代码敲出了,光看不如自己动手,总结技巧,慢慢自己就会熟能生巧。所以需要自己去想思路,尽量自己亲手动手写一遍,然后在回头想想自己写的代码,往往有很不错的效果。
发布了83 篇原创文章 · 获赞 3 · 访问量 9859

猜你喜欢

转载自blog.csdn.net/fyj13925475957/article/details/103677376