使用Java写迷宫之迷宫的生成与解决A*
- 迷宫的生成
- 迷宫的初始化
- 深度优先遍历(DFS)
- 广度优先遍历(BFS)
- 迷宫的解决
- A*算法
- 思路
- 实现
- A*代码
(非常恳请大佬能够提出您宝贵的意见,我将感激涕零!)
迷宫生成的算法思想在上一篇文章中已经介绍了,下面介绍的是具体的算法实现代码。
2是起点,3是终点,4是通路,0是墙壁
6是生成的路径答案
迷宫的生成
迷宫的初始化
void init(int width, int height, int model){
map = new int[height][width];
//初始化为墙和空白
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if (i % 2 != 0 && j % 2 != 0) {
map[i][j] = EMPTY;
} else {
map[i][j] = WALL;
}
}
}
//初始化玩家位置
int myX = 1;
int myY = 1;
map[myX][myY] = SELF;
if (model == DFSMethod) {
DFSCreate(myX, myY);
} else {
BFSCreate(myX, myY);
}
//设置此处为终点
map[height-2][width-2] = END;
//printMap(map);
}
这里的目的是为了生成
之后就是考虑如何遍历,以及如何将各个结点连接起来了。
深度优先遍历(DFS)
先附上需要的方法及变量
private final int WALL = 0;
private final int EMPTY = 1;
private final int SELF = 2;
private final int END = 3;
private final int TESTFLAG = 4;
//方向数组,便于后面加减,分别为上下左右
int[][] dir = {
{-1,0},
{1,0},
{0,-1},
{0,1}
};
//判断结点周围是否有邻居
private boolean isHaveNeighbor(int x, int y) {
for (int i = 0; i < 4; i++) {
if (inArea(x + 2 * dir[i][0],y + 2 * dir[i][1])
&& (map[x + 2 * dir[i][0]][y + 2 * dir[i][1]] == EMPTY)) {
return true;
}
}
return false;
}
深度优先遍历:
private void DFSCreate(int x, int y){
int i;
while (isHaveNeighbor(x,y)) {
i = rand.nextInt(4);
if (inArea(x + 2 * dir[i][0], y + 2 * dir[i][1])
&& map[x + 2 * dir[i][0]][y + 2 * dir[i][1]] == EMPTY) {
map[x + dir[i][0]][y + dir[i][1]] = TESTFLAG;
map[x + 2 * dir[i][0]][y + 2 * dir[i][1]] = TESTFLAG;
DFSCreate(x + 2 * dir[i][0], y + 2 * dir[i][1]);
}
}
}
对于每一个结点,如果结点周围上下左右方向的距离两格位置存在其他结点,就随机选择某个方向,如果符合EMPTY(这里也就是另一个结点),就打通两个结点中间的WALL,然后递归遍历。
这里可能会造成isHaveNeighbor方法传入的是周围有空的结点,但是i的随机数刚好是不是结点,程序就会再次取新的i,直到取到合适的i,最后就会使当前结点周围没有合适的结点,就会跳出while循环。
其实还有一种方法:
比如说如果用1234表示上下左右,其中1,2,3,4有24种排列组合(什么时候是上什么时候是下进行组合),用switch进行随机选择,只不过代码会比较长。
广度优先遍历(BFS)
这里先附上队列的代码以及相应的方法:
package exp3;
public class MyQueue {
int x;
int y;
MyQueue next;
public MyQueue(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public MyQueue getNext() {
return next;
}
}
private void insertFront (MyQueue queue) {
MyQueue p;
if (len == 0) {
front = queue;
rear = queue;
} else {
p = front;
front = queue;
front.next = p;
}
len ++;
}
private void insertRear (MyQueue queue) {
MyQueue p;
if (len == 0) {
front = queue;
rear = queue;
} else {
p = rear;
rear = queue;
p.next = rear;
}
len ++;
}
private MyQueue popFront () {
MyQueue p;
p = front;
front = front.next;
len --;
return p;
}
private MyQueue popRear () {
MyQueue p = front;
if (len == 1) {
len --;
return rear;
}
while (p.next != rear) {
p = p.next;
}
rear = p;
len --;
return rear.next;
}
BFS算法:
private void BFSCreate (int x, int y) {
int i;
MyQueue p;
while (isHaveNeighbor(x,y)) {
i = rand.nextInt(4);
if (inArea(x + 2 * dir[i][0],y + 2 * dir[i][1])
&& (map[x + 2 * dir[i][0]][y + 2 * dir[i][1]] == EMPTY)) {
map[x + 2 * dir[i][0]][y + 2 * dir[i][1]] = TESTFLAG;
map[x + dir[i][0]][y + dir[i][1]] = TESTFLAG;
if (rand.nextInt(2) == 1) {
insertFront(new MyQueue(x + 2 * dir[i][0],y + 2 * dir[i][1]));
} else {
insertRear(new MyQueue(x + 2 * dir[i][0],y + 2 * dir[i][1]));
}
}
}
if (len == 0) {
return;
}
if (rand.nextInt(2) == 1) {
p = popFront();
} else {
p = popRear();
}
BFSCreate(p.getX(),p.getY());
}
具体的思路是将结点周围的每个结点按随机顺序随机存入队列的尾部或头部,再随机从队列的尾部或头部取出(为了以防生成地图的简单,很尴尬,结果还是很简单)。
迷宫的解决
解决方法可以是深度优先解决、广度优先解决,这里使用的是A*算法。
A*算法
思路
算法需要:
搜索区域(The Search Area):搜索区域被划分为简单的二维数组,数组每个元素对应一个结点。
开放列表(Open List):将寻路过程中待检测的结点存放于Open List中,而已检测过的结点则存放于Close List中。
路径排序(Path Sorting):下一步怎么移动由以下公式确定;F(n)=G+H。F(n)为估价函数,G代表的是从初始位置Start沿着已生成的路径到指定待检测结点移动开销。H表示待检测结点到目标节点B的估计移动开销。
启发函数(Heuristics Function): H为启发函数,可以看作是一种试探,由于在找到唯一路径前,不确定在前面会出现什么障碍物,因此用了一种计算H的算法,具体可以根据实际情况决定。为了简化问题,H采用的是传统的曼哈顿距离,也就是横纵向走的距离之和。
比较晦涩,我的理解是:
首先得到self节点 ,在他周围一格范围内找到所有的通路,将它们全部放入open list中,上图放入周围3个结点(open list中存储的结点会越来越多)。
之后在open list中选择最优的结点,那就是启发函数的作用了 ,启发函数会储存所有结点的一个值,它的值越小,就最优,就把open list的那个结点放入closed list ,以及将SELF结点移动过去。
当放入了终点结点时(也就是END),就可以从closed list中从后往前遍历结点,最后获得一条通路 。
这里还需要说一下启发函数的内部构成 ,启发函数由两部分构成G和H,G代表的是从起点到当前结点走过的步数 ,H代表的是当前结点与END结点x和y轴差距之和,最后G和H相加得到F值
比如说如上图所示,箭头指向的结点与刚开始相比走了2步,所以G = 2,他与END结点的x轴坐标相差0,y轴坐标相差3,H = 0 + 3 = 3,最后F = G + H = 2 + 3 = 5.
实现
先展示定义的变量:
private int[][] map;
//x代表行长,y代表列长
private int x;
private int y;
private int EndX;
private int EndY;
private int selfX = 1;
private int selfY = 1;
ArrayList<Message> openList = new ArrayList<>();
ArrayList<Message> closedList = new ArrayList<>();
//方向数组,便于后面加减,分别为上下左右
int[][] dir = {
{-1,0},
{1,0},
{0,-1},
{0,1}
};
//地图种类定义
private final int WALL = 0;
private final int EMPTY = 1;
private final int SELF = 2;
private final int END = 3;
private final int TESTFLAG = 4;
private final int WALKED = 5;
private final int ans = 6;
//是否成功
private boolean isSucceed = false;
//生成需要的共享参数
Message tempMes;//获取选取点
//初始化与起点距离
private int len = 0;
每一个结点的信息:
class Message {
//路径增量
int F;
//开始点到当前方块的移动量
int G;
//当前方块到目标点的估算量(曼哈顿距离)
int H;
int posX;
int posY;
Message pre;
public Message(int f, int g, int h, int posX, int posY, Message pre) {
F = f;
G = g;
H = h;
this.posX = posX;
this.posY = posY;
this.pre = pre;
}
public int getF() {
return F;
}
public void setF(int f) {
F = f;
}
public int getG() {
return G;
}
public void setG(int g) {
G = g;
}
public int getH() {
return H;
}
public void setH(int h) {
H = h;
}
public int getPosX() {
return posX;
}
public void setPosX(int posX) {
this.posX = posX;
}
public int getPosY() {
return posY;
}
public void setPosY(int posY) {
this.posY = posY;
}
public Message getPre() {
return pre;
}
public void setPre(Message pre) {
this.pre = pre;
}
}
存取结点周围的所有可用结点到openList(和上面所介绍的一样,只要在周围全部都存进去):
private void storeToOpenList(int x, int y) {
for (int i = 0; i < 4; i++) {
int tempX = tempMes.getPosX();
int tempY = tempMes.getPosY();
int tempLen = len;
if ((map[x + dir[i][0]][y + dir[i][1]] == EMPTY)
|| (map[x + dir[i][0]][y + dir[i][1]] == TESTFLAG)
|| (map[x + dir[i][0]][y + dir[i][1]] == END)) {
tempX += dir[i][0];
tempY += dir[i][1];
if (!isHave(tempX, tempY)) {
//System.out.println("" + tempX + " " + tempY);
int tempG = tempLen;
int tempH = EndX + EndY - tempX - tempY;
int tempF = tempG + tempH;
Message tempPre = tempMes;
Message newMes = new Message(tempF, tempG, tempH, tempX, tempY, tempPre);
openList.add(newMes);
}
}
}
}
选openList列表中的最优值存入closedList列表中:
private void checkAndStore() {
Message chooseMes = null;
if (openList == null || openList.size() == 0) {
return;
}
for (int i = 0; i < openList.size(); i++) {
Message mes = openList.get(i);
if (mes.getPosX() == EndX && mes.getPosY() == EndY) {
chooseMes = mes;
closedList.add(chooseMes);
openList.remove(chooseMes);
isSucceed = true;
return;
}
//设置起始chooseMes
if (chooseMes == null) {
chooseMes = mes;
continue;
}
//如果当前结点比chooseMes更优
if (mes.getF() < chooseMes.getF()) {
chooseMes = mes;
continue;
}
if (mes.getF() == chooseMes.getF()) {
if (mes.getH() < chooseMes.getH()) {
chooseMes = mes;
}
}
}
closedList.add(chooseMes);
openList.remove(chooseMes);
tempMes = chooseMes;
len++;
}
这里的len表示的是从起点到当前结点移动的距离。
找到END结点后的回溯:
private void reback(Message endMes) {
Message p = endMes;
p = p.pre;
while (p != null) {
int posX = p.getPosX();
int posY = p.getPosY();
map[posX][posY] = ans;
p = p.pre;
}
}
整个流程的运行:
private void solveMethod() {
while (!isSucceed) {
checkAndStore();
storeToOpenList(tempMes.getPosX(),tempMes.getPosY());
}
Message endMes = getEndMes();
reback(endMes);
}
这里如果找到了END结点,isSucceed就会为true,就不会再继续添加了,然后在closedList列表中查询从END开始的头一个结点。
A*代码
(非常恳请大佬能够提出您宝贵的意见,我将感激涕零!)
package exp3;
import java.util.ArrayList;
public class SolveMap {
private int[][] map;
//x代表行长,y代表列长
private int x;
private int y;
private int EndX;
private int EndY;
private int selfX = 1;
private int selfY = 1;
ArrayList<Message> openList = new ArrayList<>();
ArrayList<Message> closedList = new ArrayList<>();
//方向数组,便于后面加减,分别为上下左右
int[][] dir = {
{-1,0},
{1,0},
{0,-1},
{0,1}
};
//地图种类定义
private final int WALL = 0;
private final int EMPTY = 1;
private final int SELF = 2;
private final int END = 3;
private final int TESTFLAG = 4;
private final int WALKED = 5;
private final int ans = 6;
//是否成功
private boolean isSucceed = false;
//生成需要的共享参数
Message tempMes;//获取选取点
//初始化与起点距离
private int len = 0;
SolveMap(int[][] map) {
this.map = map;
this.x = map.length;
this.y = map[0].length;
this.EndX = x - 2;
this.EndY = y - 2;
int tempG = len;
int tempH = EndX + EndY - selfX - selfY;
int tempF = tempG + tempH;
Message tempPre = null;
tempMes = new Message(tempF, tempG, tempH, selfX, selfY, tempPre);
openList.add(tempMes);
len ++;
//printMap();
System.out.println();
solveMethod();
//设置初始结点,因为在回溯时将它变为ans了
map[1][1] = SELF;
//printMap();
}
private void solveMethod() {
while (!isSucceed) {
checkAndStore();
storeToOpenList(tempMes.getPosX(),tempMes.getPosY());
}
Message endMes = getEndMes();
reback(endMes);
}
private void reback(Message endMes) {
Message p = endMes;
p = p.pre;
while (p != null) {
int posX = p.getPosX();
int posY = p.getPosY();
map[posX][posY] = ans;
p = p.pre;
}
}
private Message getEndMes() {
for (int i = closedList.size() - 1; i >= 0; i--) {
Message mes = closedList.get(i);
if (mes.getPosX() == EndX && mes.getPosY() == EndY) {
return mes;
}
}
return null;
}
private void checkAndStore() {
Message chooseMes = null;
if (openList == null || openList.size() == 0) {
return;
}
for (int i = 0; i < openList.size(); i++) {
Message mes = openList.get(i);
if (mes.getPosX() == EndX && mes.getPosY() == EndY) {
chooseMes = mes;
closedList.add(chooseMes);
openList.remove(chooseMes);
isSucceed = true;
return;
}
//设置起始chooseMes
if (chooseMes == null) {
chooseMes = mes;
continue;
}
//如果当前结点比chooseMes更优
if (mes.getF() < chooseMes.getF()) {
chooseMes = mes;
continue;
}
if (mes.getF() == chooseMes.getF()) {
if (mes.getH() < chooseMes.getH()) {
chooseMes = mes;
}
}
}
closedList.add(chooseMes);
openList.remove(chooseMes);
tempMes = chooseMes;
len++;
}
private void storeToOpenList(int x, int y) {
for (int i = 0; i < 4; i++) {
int tempX = tempMes.getPosX();
int tempY = tempMes.getPosY();
int tempLen = len;
if ((map[x + dir[i][0]][y + dir[i][1]] == EMPTY)
|| (map[x + dir[i][0]][y + dir[i][1]] == TESTFLAG)
|| (map[x + dir[i][0]][y + dir[i][1]] == END)) {
tempX += dir[i][0];
tempY += dir[i][1];
if (!isHave(tempX, tempY)) {
//System.out.println("" + tempX + " " + tempY);
int tempG = tempLen;
int tempH = EndX + EndY - tempX - tempY;
int tempF = tempG + tempH;
Message tempPre = tempMes;
Message newMes = new Message(tempF, tempG, tempH, tempX, tempY, tempPre);
openList.add(newMes);
}
}
}
}
private boolean isHave(int x, int y) {
//判断openList中是否存在添加的message
if (openList != null) {
for (int i = 0; i < openList.size(); i++) {
Message message = openList.get(i);
if (message.getPosX() == x && message.getPosY() == y) {
return true;
}
}
}
//判断closedList中是否存在添加的message
if (closedList != null) {
for (int i = 0; i < closedList.size(); i++) {
Message message = closedList.get(i);
if (message.getPosX() == x && message.getPosY() == y) {
return true;
}
}
}
return false;
}
public int[][] getMap() {
return map;
}
//test
private void printMap() {
for (int i = 0; i < x; i++) {
for (int j = 0; j < y; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
}
class Message {
//路径增量
int F;
//开始点到当前方块的移动量
int G;
//当前方块到目标点的估算量(曼哈顿距离)
int H;
int posX;
int posY;
Message pre;
public Message(int f, int g, int h, int posX, int posY, Message pre) {
F = f;
G = g;
H = h;
this.posX = posX;
this.posY = posY;
this.pre = pre;
}
public int getF() {
return F;
}
public void setF(int f) {
F = f;
}
public int getG() {
return G;
}
public void setG(int g) {
G = g;
}
public int getH() {
return H;
}
public void setH(int h) {
H = h;
}
public int getPosX() {
return posX;
}
public void setPosX(int posX) {
this.posX = posX;
}
public int getPosY() {
return posY;
}
public void setPosY(int posY) {
this.posY = posY;
}
public Message getPre() {
return pre;
}
public void setPre(Message pre) {
this.pre = pre;
}
}