【编程题 深度优先遍历 】HJ43 迷宫问题(详细注释 易懂)

题目描述

题目链接:   迷宫问题_牛客题霸_牛客网 (nowcoder.com)

定义一个二维数组 N*M ,如 5 × 5 数组下所示:

int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};

它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的路线。入口点为[0,0],既第一格是可以走的路。

数据范围: 2 <= n,m <= 10 , 输入的内容只包含 0 <= val <= 1

输入描述:

输入两个整数,分别表示二维数组的行数,列数。再输入相应的数组,其中的1表示墙壁,0表示可以走的路。数据保证有唯一解,不考虑有多解的情况,即迷宫只有一条通道。

输出描述:

左上角到右下角的最短路径,格式如样例所示。

示例1

输入:

5 5
0 1 0 0 0
0 1 1 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0

复制输出:

(0,0)
(1,0)
(2,0)
(2,1)
(2,2)
(2,3)
(2,4)
(3,4)
(4,4)

示例2

输入:

5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 1
0 1 1 1 0
0 0 0 0 0

输出:

(0,0)
(1,0)
(2,0)
(3,0)
(4,0)
(4,1)
(4,2)
(4,3)
(4,4)

说明:

注意:不能斜着走!! 

题目解读:

      给一个二维数组[ m ][ n ] ,里面只有数值  0 和 1,0 表示通路,1 表示 墙,入口在 (0,0),出口在 (m-1,n-1)处,要求打印出唯一通路的路径 ,最后在打印的时候,要单独添加 括号。题目还是挺好懂的,但其实并不好做。

解题思想:

      这道题,我们能想到用 深度优先遍历解题。 大致思路是 给递归函数传入一个 顺序表,让它去收录遍历过程中的路径(为了 记录路径,要设一个坐标类,把每一个坐标当作一个对象存储在顺序表中)。

       首先向下遍历,如果没有超出边界,并且不是墙,就继续往下遍历,当以上两个条件任意一个不满足,就在当前位置(不是起始位置),向右遍历,如果右遍历发现不满足条件,那就左遍历,左遍历发现也不满足条件,那就上遍历,上遍历也不满足条件,行,那就把当前坐标从顺序表里删掉,然后回到上一个位置,继续向下,向右,向左,向上遍历。

      但这个思路,大家也能发现问题,那就是 遍历过的位置会被重复遍历,那怎么解决这个问题呢? 我们可以把遍历到的每个当前位置,赋值为 1,也就是 通路化墙,这样它在回到上一个位置,发现是墙,就不会把这个位置坐标再放入顺序表了,而且这样也不影响,它在上一个位置 继续向右,向左,向上遍历(向下遍历已经遍历过了),最后返回顺序表打印,就结束了。 

代码注释:

import java.util.*;
// 设一个 坐标类
class Position{
    int x = 0;
    int y =0;
    public Position(int x,int y){
        this.x = x;
        this.y =y;
    }
    
}
public class Main{
   // 这个函数用于进行深度遍历,并存储路途的坐标
    public static boolean findShortPath(List<Position> path, int row,int column,int[][] map,int x,int y){
         // 每次进入函数,先把坐标存储到顺序表中
         path.add(new Position(x,y));
       // 这是递归终止条件,如果到达[m-1 ][ n-1 ] 位置了,就可以返回了
      if(x == row-1 && y == column -1)
         return true;
   // 把当前遍历到的每个位置,赋值为1, 避免重复遍历
         map[x][y] = 1;
          // 这是向下遍历 
          if(x+1 < row && map[x+1][y] == 0){
               if(findShortPath(path,row,column,map,x+1, y)) 
                   return true;
               
                  
            }
             // 这是向右遍历 
           if(y+1 < column && map[x][y+1] == 0){
               if(findShortPath(path,row,column,map,x, y+1))  
                   return true;
            }
            // 这是向左遍历 
           if(y > 0  && map[x][y-1] == 0){
                if(findShortPath(path,row,column,map,x, y-1))
                return true;
            }
            // 这是向右遍历 
           if(x >0 && map[x-1][y] == 0){
                if(findShortPath(path,row,column,map,x-1, y))
                return true;
            }
           // 如果当前位置 四个方向,都符合遍历条件,就从顺序表中 删除当前坐标
           path.remove(path.size()-1);
           return false;
       
    }
    public static void main(String[] args){
        Scanner scan =new Scanner(System.in);
        int row = scan.nextInt();
        int column = scan.nextInt();
        int[][] map = new int[row][column];
        for(int i=0;i< row;i++){
            for(int j=0;j< column;j++){
                map[i][j] = scan.nextInt();
            }
        }
          List<Position> path = new ArrayList<>();
        findShortPath(path,row,column,map,0,0);
        for(Position position: path){
       // 注意题目要求的输出,你要单独添加 括号
            System.out.println("("+position.x+","+position.y+")");
        }
        
    }
}

代码讲解:

      看完上面的解题思路 和 代码注释,解题思想应该是懂了,但对于 遍历的 条件应该还是有疑问的。

      首先,为什么 不是用一个循环,对当前坐标,进行依次递归遍历,而是用了 四个if 语句,分别递归遍历。因为你要明白 用循环进行递归 和 四个if 语句递归的区别,比如 出口在 (4,4),你从 (3,4)递归到了(4,4),你到达了出口,此时依照你的终止条件,它返回了上一层递归,可是上一层递归里面,它没有执行完,它不会就此罢手,所以它返回到(3,4)之后,会继续向右遍历,右遍历不符合条件,就向左遍历,左遍历符合条件,它就继续遍历,继续把新的坐标放入顺序表中,所以你就得不到正确的 路径 。    而如果是四个   if语句递归,那么它从(4,3) 回到(3,4)之后,每个if语句,后面都有个 return ,这样它就执行 return 语句,就不会再向右,向左,向上遍历了,然后一直 return ,直到回到第一层递归,函数执行完毕,主函数拿到 顺序表,然后打印即可。   这里要注意,顺序表是引用,所以传一个引用,递归函数放入里面的坐标,主函数可以直接获取到。 

     第二个问题,为什么要用 Boolean 作为函数的返回条件, 前面说了,由于这个题的特殊性,我们需要四个if 语句来控制递归。当找到出口,递归返回过程中,它不能继续执行其他代码。但是如果没有找到出口,它返回递归的过程中,依然要继续向右,向左,向上遍历,那如何控制这种情况呢,我们就用 Boolean值控制,如果 没找到出口,当前坐标的四个方向又遍历完了,那就返回一个 false给上一层,这样上一层 if 语句接收到 false后,就不执行它里面的 return true语句了  ,而是执行剩余的 if 语句。 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值