老鼠走迷宫问题(2016百度机试题)

问题描述:

        在迷宫某处放一大块奶酪,把一只老鼠Mooshak放入迷宫。

        迷宫以二维数组表示,0表示墙,1表示Mooshak可以移动的路径,9表示奶酪所在位置。Mooshak从迷宫左上角(0,0)开始移动。

        编写一个MazePath类的方法isPath,判断Mooshak是否能到达奶酪所在地。如果Mooshak和奶酪之间存在一条路径,isPath方法返回true,否则返回false,Mooshak不能离开迷宫或。

Mooshak可得到奶酪的8*8迷宫示例如下:

        老鼠走迷宫问题(2016百度机试题)_第1张图片


解法一: 利用回溯法求解。

         回溯法又称作”试探法“,是一种系统地搜索问题的解的算法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。

         将回溯法应用到上述待解决的问题,就是让我们贪吃的小老鼠从起点位置开始,每次选取一个可前进的方向往前走,到达新的位置后,检查是否可以继续前进:如果已经走进死胡同,原路返回前一位置尝试其他方向;否则,就从新位置继续前进。如此循环进行,直到找到奶酪为止。 

         代码实现涉及的2个细节:

        1.需要对所有已经过的位置做上标记(标为 -1) ,防止回溯试探过程走进之前走过的位置,导致程序陷入死循环;

        2.维护一个stack,每当走进新位置就将此位置信息压栈,如果陷入死胡同就弹栈,获取前一位置信息,实现原路返回的效果。如果所有可能的路径都尝试完(栈被弹空)仍未找到奶酪,说明起点和奶酪之间不存在路径。

        

        下面是代码实现:

import java.util.Stack;

public class MazePath {
  private static final int PASSED   = -1;    // 已经过标识
  private static final int WALL      = 0;    // 墙
  private static final int PATH      = 1;    // 可移动的路径
  private static final int CHEESE    = 9;    // 奶酪
  
  private static final int STEP_FORWARD = 1;  // 向前一步
  private static final int STEP_BACK    = -1; // 向后一步
  
  private int[][] grid;      // 迷宫
  private final int x_len;   // 迷宫行数
  private final int y_len;   // 迷宫列数
  private int x;             // 当前位置纵坐标
  private int y;             // 当前位置横坐标
  
  public MazePath(int[][] grid) {
    this.grid = grid;
    x_len = grid.length;
    y_len = grid[0].length;
  }
  
  public boolean isPath() {
    if (grid == null || grid[x][y] == WALL) // 处理异常输入
      return false;
    
    Stack stack = new Stack(); // 栈,用于记录已经过的位置
    stack.push(new Point(x, y));
    
    // 栈被弹空,则说明所有路径都已被尝试过
    while (!stack.isEmpty()) {
      // 判断当前位置是否有奶酪,有则返回true,无则将当前位置标记为已经过
      if (grid[x][y] == CHEESE) return true;
      else                      grid[x][y] = PASSED;
      
      // 可以向左走一步
      if (y > 0 
          && grid[x][y - 1] != PASSED 
          && grid[x][y - 1] != WALL) {
         turn_left();
         stack.push(new Point(x, y));
         continue;
      }
      // 可向右走一步
      if (y < y_len-1 
          && grid[x][y + 1] != PASSED
          && grid[x][y + 1] != WALL) {
        turn_right();
        stack.push(new Point(x, y));
        continue;
      }
      // 可向上走一步
      if (x > 0 
          && grid[x - 1][y] != PASSED
          && grid[x - 1][y] != WALL) {
        turn_up();
        stack.push(new Point(x, y));
        continue;
      }
      // 可向下走一步
      if (x < x_len-1 
          && grid[x + 1][y] != PASSED
          && grid[x + 1][y] != WALL) {
        turn_down();
        stack.push(new Point(x, y));
        continue;
      }
      
      // 无可前进方向,返回上一位置
      Point pos = stack.pop();
      x = pos.i;
      y = pos.j;
    }
    return false; // 所有路径已探索完,未找到奶酪
  }
  
  private void turn_left() {
    if (y <= 0) {
      throw new IllegalArgumentException("Go left when y =" + y);
    }
    y = y + STEP_BACK;
  }
  
  private void turn_right() {
    if (y >= y_len - 1) {
      throw new IllegalArgumentException("Go right when y =" + y);
    }
    y = y + STEP_FORWARD;
  }
  
  private void turn_up() {
    if (x <= 0) {
      throw new IllegalArgumentException("Go up when x =" + x);
    }
    x = x + STEP_BACK;
  }
  
  private void turn_down() {
    if (x >= x_len - 1) {
      throw new IllegalArgumentException("Go down when x =" + x);
    }
    x = x + STEP_FORWARD;
  }
  
  // 内部类:已经过的位置
  class Point {
    private int i;
    private int j;
    
    public Point(int i, int j) {
      this.i = i;
      this.j = j;
    }
    
    public String toString() {
      return "(" + i +", " + j +")";
    }
  }
  // 测试代码
  public static void main(String[] args) {
    int[][] grid = new int[][]
        {{1, 1, 1, 1, 1, 0, 0, 1}, 
         {1, 0, 0, 0, 1, 1, 1, 1}, 
         {1, 0, 0, 0, 0, 0, 0, 1},
         {0, 0, 1, 0, 0, 0, 0, 9},
         {1, 1, 1, 0, 1, 1, 0, 1},
         {1, 0, 1, 0, 0, 1, 0, 1},
         {1, 0, 0, 0, 0, 1, 0, 1},
         {1, 1, 1, 1, 1, 1, 1, 1}};

    MazePath obj = new MazePath(grid);
    System.out.println(obj.isPath());
  }
    
   
}

 

解法二:利用 并查集(union-find)算法判断起点和奶酪之间的连通性。

       我们认为迷宫中互不相交的连通路径各自成为一个联结体,组成联结体的每个位置是相互连通的。如下图中所示,迷宫中连通路径组成了2个联结体(分别被标为红色、蓝色)。

老鼠走迷宫问题(2016百度机试题)_第2张图片

       算法首先对迷宫中每个位置进行遍历,对各位置的连通性进行初始化,也就是建立各联结体的连通信息。

       (关于并查集算法详见:http://blog.csdn.net/dm_vincent/article/details/7655764

       然后,判断起点和奶酪位置之间是否连通即可,若连通则说明老鼠可以吃到奶酪。

实现代码:

import edu.princeton.cs.algs4.WeightedQuickUnionUF;

public class MazePath2 {
  private static final int WALL      = 0;    // 墙
  private static final int PATH      = 1;    // 可移动的路径
  private static final int CHEESE    = 9;    // 奶酪
  
  private int[][] grid;      // 迷宫
  private final int x_len;   // 迷宫行数
  private final int y_len;   // 迷宫列数
  private int x;             // 奶酪位置纵坐标
  private int y;             // 奶酪位置横坐标
  
  public MazePath2(int[][] grid) {
    this.grid = grid;
    x_len = grid.length;
    y_len = grid[0].length;
    x = Integer.MIN_VALUE;
    y = Integer.MAX_VALUE;
  }
  
  public boolean isPath() {
    if (grid == null || grid[0][0] == WALL) // 处理异常输入
      return false;
    
    // 并查集数据结构
    WeightedQuickUnionUF uf = new WeightedQuickUnionUF(x_len * y_len);
    // 初始化各位置连通性
    for (int i = 0; i < x_len; i++) {
      for (int j = 0; j < y_len; j++) {
        
        if (grid[i][j] != WALL) {
          
          // 找到奶酪所在位置
          if (grid[i][j] == CHEESE) {
            x = i;
            y = j;
          }
          
          if (i > 0 && grid[i - 1][j] == PATH) {
            uf.union(transferIndex(i, j), transferIndex(i-1, j));
          }
          if (i < x_len-1 && grid[i + 1][j] == PATH) {
            uf.union(transferIndex(i, j), transferIndex(i+1, j));
          }
          if (j > 0 && grid[i][j - 1] == PATH) {
            uf.union(transferIndex(i, j), transferIndex(i, j-1));
          }
          if (j < y_len-1 && grid[i][j + 1] == PATH) {
            uf.union(transferIndex(i, j), transferIndex(i, j+1));
          }
        }
        
      }
    }
    
    if ((x | y) < 0)  return false; // 迷宫内无奶酪
    
    // 判断起点和奶酪位置的连通性,并返回
    return uf.connected(transferIndex(0, 0), transferIndex(x, y));
  }

  // 将二维数组索引转换成一维数组索引
  private int transferIndex(int i, int j) {
    return i * y_len + j;
  }
    
  public static void main(String[] args) {
    int[][] grid = new int[][]
        {{1, 0, 9, 1, 1, 0, 0, 1}, 
         {0, 0, 0, 0, 1, 1, 1, 1}, 
         {1, 0, 0, 0, 0, 0, 0, 1},
         {1, 0, 1, 0, 0, 0, 0, 0},
         {1, 1, 0, 0, 1, 0, 1, 1},
         {1, 0, 1, 0, 0, 1, 0, 1},
         {1, 0, 0, 0, 0, 1, 0, 1},
         {1, 1, 1, 1, 1, 1, 1, 1}};

    MazePath2 obj = new MazePath2(grid);
    System.out.println(obj.isPath());
  }
    
   
}

你可能感兴趣的:(算法题)