要熟练得掌握一种数据结构,要经过大量的练习,而将数据结构应用于实际用用中则是一种非常好的锻炼方式。
此次便是应用java来实现 迷宫求解 这个经典的程序设计问题。
迷宫如上图所示,其中,'#'为迷宫的墙,' '为迷宫的路,'∩'是迷宫的入口,'∏'为迷宫的出口,'√'为访问过的路,'→↓←↑'为访问过的路,且箭头指向为下一步的走向。
迷宫求解的算法的基本思想是:若当前位置“可通”,则纳入“当前路径”,并继续朝“下一位置”探索,即切换“下一位置”为“当前位置”,如此重复直至到达出口;若当前位置“不可通”,则应顺着“来向”退回到“前一通道块”,然后朝着除“来向”之外的其他方向继续探索,若该通道块的四周4个方块均为“不可通”,则应从“当前路径”上删除该通道块。
假设以栈记录“当前路径”,则栈顶中存放的是“当前路径上最后一个通道块”。由此,“纳入路径”的操作即为“当前位置入栈”;“从当前位置删除该通道块”的操作即为“出栈”。
以伪代码来简单描述算法如下:
do {
若当前位置可通,
则{
将当前位置压入栈顶;//纳入路径
若该位置是出口位置,则结束;//求得路径存放在栈中
否则切换当前位置的东邻方块为新的当前位置;
}
否则,{
若 栈不空且栈顶位置尚有其他方向未经探索,
则设定新的当前位置为沿顺时针方向旋转找到的栈顶位置的下一相邻块;
若 栈不空但栈顶位置的四周均不可通,
则{
删去栈顶位置;//从路径中删去该通道块
若 栈不空,则重新测试新的栈顶位置,
直至找到一个可通的相邻块或出栈至栈空;
}
}
} while (栈不空);
特别说明一下的是,此处所谓可通,指的是没有访问过的路、迷宫入口、迷宫出口。
寻找路径的核心代码如下:
/**
* 寻找迷宫出口
*/
void findMazePath() {
Position curPos;// 当前位置
int curStep;// 寻找的步数
mazeResult = new Stack<MazePoint>();// 存放路径的栈
MazePoint mPoint = new MazePoint();// 记录当前位置的信息(坐标,步数,方向)
// 开始寻找迷宫出口,先将入口位置设定为当前位置
System.out.println("现在开始寻找迷宫出口...");
curPos = mazeStart;
curStep = 1;
do {
if (isPass(curPos)) {
// 当前位置可通过,且没有走过
markPoint(curPos);// 标记当前位置为路过
mPoint.direction = MOVE_RIGHT;
mPoint.order = curStep;
mPoint.point = curPos;
savePoint(mPoint);// 将当前位置的信息加入结果路径中
if (curPos.equals(mazeEnd)) {// 判断当前位置是否出口位置
// 已经到达出口
setMazePath();
return;
}
curPos = findNextPoint(curPos, MOVE_RIGHT);// 寻找下一个位置
curStep++;
} else {
// 当前位置不可通过
if (!mazeResult.isEmpty(mazeResult)) {
mPoint = mazeResult.pop(mazeResult);// 弹出上一个错误方向的位置
while (mPoint.direction == MOVE_UP
&& !mazeResult.isEmpty(mazeResult)) {
markPoint(mPoint.point);// 标记走过但无效的路
mPoint = mazeResult.pop(mazeResult);// 弹出并获取上一个位置
}
if (mPoint.direction < MOVE_UP) {// 若当前位置还有其他方向没有寻找过
mPoint.direction++;// 寻找当前位置下一个方向的位置
savePoint(mPoint);// 保存当前寻找方向
curPos = findNextPoint(mPoint.point, mPoint.direction);// 寻找下一个方向的位置
}
}
}
printMazeMap();// 打印寻找迷宫的每一步,若只需结果可将该行注释
} while (!mazeResult.isEmpty(mazeResult));
System.out.println("此迷宫无出口");
return;
}
完整代码如下:
Position.java
package algorithm.mazepath;
/**
* 迷宫中的点的坐标
*
* @author Abyss_CMG
*
*/
public class Position {
public int x;// 坐标轴中的x
public int y;// 坐标轴中的y
Position() {
}
Position(int x, int y) {
this.x = x;
this.y = y;
}
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof Position)) {
return false;
}
Position position = (Position) obj;
return (this.x == position.x && this.y == position.y);
}
}
MazePoint.java
package algorithm.mazepath;
/**
* 迷宫中某个位置的信息
*
* @author Abyss_CMG
*
*/
public class MazePoint {
int order;// 当前位置在结果路径上的序号
Position point = new Position();// 当前位置的坐标
int direction;// 当前位置移动方向
}
MazePath
.java
package algorithm.mazepath;
import java.util.Random;
import java.util.Scanner;
/**
* 迷宫求解简单实现
*
* @author Abyss_CMG
*
*/
public class MazePath {
private static final int DEFAULT_MAP_SIZE = 10;// 迷宫默认尺寸10*10
private static final int WALL = 1; // 迷宫的墙
private static final int ROAD = 2;// 迷宫的路
private static final int MAZE_IN = 0;// 迷宫入口
private static final int MAZE_OUT = 9;// 迷宫出口
private static final int VISIT = 3;// 已访问的路
private static final int MOVE_RIGHT = 5;// 向右移动
private static final int MOVE_DOWN = 6;// 向下移动
private static final int MOVE_LEFT = 7;// 向左移动
private static final int MOVE_UP = 8;// 向上移动
private int mazeMap[][] = null;// 存放迷宫的坐标
private int rows;// 迷宫的行数
private int columns;// 迷宫的列数
private Position mazeStart, mazeEnd;// 迷宫的入口,出口位置
private Stack<MazePoint> mazeResult;// 存放迷宫路径的栈
/**
* 默认初始化迷宫
*/
MazePath() {
this(DEFAULT_MAP_SIZE, DEFAULT_MAP_SIZE);
}
/**
* 初始化迷宫
*
* @param row
* 行数
* @param column
* 列数
*/
MazePath(int row, int column) {
if (mazeMap == null) {
mazeMap = new int[row][column];
rows = row;
columns = column;
}
initMazeMap();
}
/**
* 随机产生迷宫的墙和路
*/
void initMazeMap() {
Random random = new Random();
for (int i = 0; i < columns; i++) {
mazeMap[0][i] = WALL;
mazeMap[rows - 1][i] = WALL;
}
for (int i = 1; i < rows - 1; i++) {
mazeMap[i][0] = WALL;
mazeMap[i][rows - 1] = WALL;
for (int j = 1; j < rows - 1; j++) {
if (random.nextInt(10) <= 2) {// 产生路和强
mazeMap[i][j] = WALL;
} else {
mazeMap[i][j] = ROAD;
}
}
}
}
/**
* 设置迷宫的入口和出口
*/
void setMapInOut() {
Scanner scanner = new Scanner(System.in);
mazeStart = new Position();
mazeEnd = new Position();
System.out.println("请输入迷宫入口<行,列>(以空格分隔行,列):");
mazeStart.x = scanner.nextInt();
mazeStart.y = scanner.nextInt();
mazeMap[mazeStart.x][mazeStart.y] = MAZE_IN;
System.out.println("请输入迷宫出口<行,列>(以空格分隔行,列):");
mazeEnd.x = scanner.nextInt();
mazeEnd.y = scanner.nextInt();
mazeMap[mazeEnd.x][mazeEnd.y] = MAZE_OUT;
}
/**
* 输出迷宫
*/
void printMazeMap() {
System.out.println("迷宫如下:");
System.out.println("#为迷宫的墙, 为迷宫的路,∩为迷宫入口,∏为迷宫出口,箭头为前进方向,√为访问过的路");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
switch (mazeMap[i][j]) {
case WALL:// 迷宫的墙
System.out.print("#");
break;
case ROAD:// 迷宫内的路
System.out.print(" ");
break;
case MAZE_IN:// 迷宫入口
System.out.print("∩");
break;
case MAZE_OUT:// 迷宫出口
System.out.print("∏");
break;
case VISIT:// 迷宫中访问过的路
System.out.print("√");
break;
case MOVE_RIGHT:// 从当前位置向右移
System.out.print("→");
break;
case MOVE_DOWN:// 从当前位置向下移
System.out.print("↓");
break;
case MOVE_LEFT:// 从当前位置向左移
System.out.print("←");
break;
case MOVE_UP:// 从当前位置向上移
System.out.print("↑");
break;
}
System.out.print(" ");
}
System.out.println();
}
}
/**
* 寻找下一个位置坐标
*
* @param curPos
* 当前位置坐标
* @param dir
* 寻找方向
* @return 下一个位置的坐标
*/
Position findNextPoint(Position curPos, int dir) {
Position desPos = new Position();
switch (dir) {
case MOVE_RIGHT:// 从当前位置向右移
desPos.x = curPos.x;
desPos.y = curPos.y + 1;
break;
case MOVE_DOWN:// 从当前位置向下移
desPos.x = curPos.x + 1;
desPos.y = curPos.y;
break;
case MOVE_LEFT:// 从当前位置向左移
desPos.x = curPos.x;
desPos.y = curPos.y - 1;
break;
case MOVE_UP:// 从当前位置向上移
desPos.x = curPos.x - 1;
desPos.y = curPos.y;
break;
}
return desPos;
}
/**
* 寻找迷宫出口
*/
void findMazePath() {
Position curPos;// 当前位置
int curStep;// 寻找的步数
mazeResult = new Stack<MazePoint>();// 存放路径的栈
MazePoint mPoint = new MazePoint();// 记录当前位置的信息(坐标,步数,方向)
// 开始寻找迷宫出口,先将入口位置设定为当前位置
System.out.println("现在开始寻找迷宫出口...");
curPos = mazeStart;
curStep = 1;
do {
if (isPass(curPos)) {
// 当前位置可通过,且没有走过
markPoint(curPos);// 标记当前位置为路过
mPoint.direction = MOVE_RIGHT;
mPoint.order = curStep;
mPoint.point = curPos;
savePoint(mPoint);// 将当前位置的信息加入结果路径中
if (curPos.equals(mazeEnd)) {// 判断当前位置是否出口位置
// 已经到达出口
setMazePath();
return;
}
curPos = findNextPoint(curPos, MOVE_RIGHT);// 寻找下一个位置
curStep++;
} else {
// 当前位置不可通过
if (!mazeResult.isEmpty(mazeResult)) {
mPoint = mazeResult.pop(mazeResult);// 弹出上一个错误方向的位置
while (mPoint.direction == MOVE_UP
&& !mazeResult.isEmpty(mazeResult)) {
markPoint(mPoint.point);// 标记走过但无效的路
mPoint = mazeResult.pop(mazeResult);// 弹出并获取上一个位置
}
if (mPoint.direction < MOVE_UP) {// 若当前位置还有其他方向没有寻找过
mPoint.direction++;// 寻找当前位置下一个方向的位置
savePoint(mPoint);// 保存当前寻找方向
curPos = findNextPoint(mPoint.point, mPoint.direction);// 寻找下一个方向的位置
}
}
}
printMazeMap();// 打印寻找迷宫的每一步,若只需结果可将该行注释
} while (!mazeResult.isEmpty(mazeResult));
System.out.println("此迷宫无出口");
return;
}
/**
* 若能找到迷宫出口,则修改迷宫行进路径
*/
void setMazePath() {
MazePoint path = null;
System.out.println("迷宫有结果!!");
while (!mazeResult.isEmpty(mazeResult)) {
path = mazeResult.pop(mazeResult);
if (!path.point.equals(mazeEnd) && !path.point.equals(mazeStart)) {
mazeMap[path.point.x][path.point.y] = path.direction;// 按行进方向修改迷宫
}
}
}
/**
* 当前位置是否能通过,若当前位置符合以下条件则为能通过:没走过的路、为迷宫入口、为迷宫出口
*
* @param curPos
* 当前位置
* @return 若能通过,则返回true,否则返回false
*/
boolean isPass(Position curPos) {
return (mazeMap[curPos.x][curPos.y] == ROAD
|| mazeMap[curPos.x][curPos.y] == MAZE_IN || mazeMap[curPos.x][curPos.y] == MAZE_OUT);
}
/**
* 修改当前位置的标记
*
* @param curPos
*/
void markPoint(Position curPos) {
if (mazeMap[curPos.x][curPos.y] == MAZE_IN
|| mazeMap[curPos.x][curPos.y] == MAZE_OUT) {
return;
}
mazeMap[curPos.x][curPos.y] = VISIT; // 将当前位置标记为已走过
}
/**
* 将当前位置的信息压入栈中
*
* @param sPoint
* 待保存的位置的信息
*/
void savePoint(MazePoint sPoint) {
MazePoint tmp = new MazePoint();
tmp.direction = sPoint.direction;
tmp.order = sPoint.order;
tmp.point.x = sPoint.point.x;
tmp.point.y = sPoint.point.y;
mazeResult.push(mazeResult, tmp);
}
}
测试代码:
package algorithm.mazepath;
public class Simple {
public static void main(String[] args) {
MazePath maze = new MazePath();
maze.printMazeMap();
maze.setMapInOut();
maze.printMazeMap();
maze.findMazePath();
maze.printMazeMap();
}
}
测试结果1:
测试结果2:
该算法有一个很大的问题,由于查询“下一位置”的顺序是右、下、左、上,因此在迷宫入口在左上角,出口在右下角时,能够很快找到迷宫路径,但是如果迷宫入口在右上角,迷宫出口在左上角时,就会寻找得很慢。
链接为完整代码的下载地址:http://download.csdn.net/detail/shenyuan2/7979819