您的位置:首页 > 编程语言 > Java开发

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

2016-09-16 17:13 267 查看
问题描述:

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

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

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

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

        


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

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

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

         代码实现涉及的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<Point> stack = new Stack<Point>(); // 栈,用于记录已经过的位置
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个联结体(分别被标为红色、蓝色)。



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

       (关于并查集算法详见: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());
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息