简介
回顾本科毕业设计的一个小项目,用Java实现小老鼠走迷宫,最终找到粮仓的小游戏。界面可视化用的Java Swing,模块则是分为迷宫的创建、寻路演示、玩家找寻出口、参数设置这几个模块。由于时间太久了,源码丢失,只能用毕业设计的文档,重新敲了一遍迷宫生成算法、寻路算法。接下来就分享一下迷宫创建的三大算法。
迷宫表示
int[][][] maze=new int[rows][cols][5];
用maze数组来记录创建的迷宫。注意这个是一个三维数组!规定:每个迷宫单元有左下右上(左开始,逆时针)四面墙,分别用maze[row][col][0],maze[row][col][1]…maze[row][col][3]来记录。其中,默认maze[row][col][0-3]为0,即表示有墙存在。最后,maze[row][col][4]记录的是访问标志位,默认为0,表示未访问。注意:上述表示应区别如下迷宫表示(力扣迷宫题为如下表示),0表示可以通过,1表示为墙,两种迷宫表示可以相互转换。
00100
00000
00010
11011
00000
几个重要的成员变量,rows、cols代表迷宫的行数及列数。
//迷宫的行列数
int rows=4;
int cols=4;
//迷宫起始位置
int row;
int col;
深度优先生成(非递归)
深度优先生成算法的非递归思路为:
第一,判断上下左右四个方向是否可以走。(走过或越界均不可走)
第二,将可以走的路加入到栈中。(每次循环开头都出栈)
第三,随机选取相邻的一个可走路径,并打通这两个迷宫单元的路径。
//dfs迷宫创建
public void dfsCreateM() {
//根据rows、cols重新申请空间
maze=new int[rows][cols][5];
Stack<Integer> stack=new Stack<Integer>();
int row=this.row;
int col=this.col;
stack.push(row);
stack.push(col);
while(!stack.isEmpty()) {
//注意先出栈的是col
col=stack.pop();
row=stack.pop();
maze[row][col][4]=1;
//判断当前迷宫位置相邻的四个方向是否被访问过,并且要做越界判断。未被访问则加入候选访问str中
StringBuffer str=new StringBuffer();
if(col>0&&maze[row][col-1][4]==0) {
str.append('L');
stack.push(row);
stack.push(col-1);
}
if(row>0&&maze[row-1][col][4]==0) {
str.append('U');
stack.push(row-1);
stack.push(col);
}
if(col<cols-1&&maze[row][col+1][4]==0) {
str.append('R');
stack.push(row);
stack.push(col+1);
}
if(row<rows-1&&maze[row+1][col][4]==0) {
str.append('D');
stack.push(row+1);
stack.push(col);
}
//只要有候选路可走就随机选取一个方向并打通这两个迷宫单元的路径
//注意回溯到未被选中的节点时,该节点不会与回溯的节点产生路径
if(str.length()!=0) {
//若候选路径数为2,则生成[0,2)区间的整数值
int random =new Random().nextInt(str.length());
//打通两个迷宫单元的路径
if(str.charAt(random)=='L') {
maze[row][col][0] =1;
maze[row][--col][2] =1;
}
if(str.charAt(random)=='U') {
maze[row][col][3] =1;
maze[--row][col][1] =1;
}
if(str.charAt(random)=='R') {
maze[row][col][2] =1;
maze[row][++col][0] =1;
}
if(str.charAt(random)=='D') {
maze[row][col][1] =1;
maze[++row][col][3] =1;
}
}
}
}
随机普里姆生成
说到随机prim算法,可以先回顾一下最小生成树中的普里姆算法。算法的前提是在带权无向图中进行的。首先,随机选取一个节点,将其作为一颗树。然后,选择与这棵树相连且权值最小的边,将其归入到树中。一直重复上面的步骤,直到所有节点都加入到树中了。
随机普里姆算法就很好理解了。即在选取边的时候不选取权值最小的,而是一视同仁,随机选取。这就是随机普里姆生成的思想了。深度优先生成非递归需要借助栈来完成,而普里姆算法只需维护一个LinkedList数组(有大量的插入删除,不要用ArrayList)。在每次选取路径的时候和之前加入到数组中的路一起随机,这样生成的迷宫更加复杂。
//prim迷宫创建
public void randomPrimCreateM() {
//根据rows、cols重新申请空间,init
maze=new int[rows][cols][5];
int row=this.row;
int col=this.col;
List<Integer> list=new LinkedList<Integer>();
list.add(row);
list.add(col);
while(!list.isEmpty()) {
/* 1.在list中随机选择一个路径,这是随机prim算法核心思想。
* 2.若不随机选取,按照路径权值大小排序,选最小的那个,那就变为最小生成树中prim算法思想。
* */
int random =new Random().nextInt((int)(list.size()/2));
//将随机选取的路径移出list
row=list.remove(2*random);
col=list.remove(2*random);//移出后col存储位置会前移一位
maze[row][col][4]=1;
StringBuffer str=new StringBuffer();
if(col>0) {
if(maze[row][col-1][4]==1)
str.append('L');
else if(maze[row][col-1][4]==0) {
list.add(row);
list.add(col-1);
maze[row][col-1][4]=2;
}
}
if(row>0) {
if(maze[row-1][col][4]==1)
str.append('U');
else if(maze[row-1][col][4]==0) {
list.add(row-1);
list.add(col);
maze[row-1][col][4]=2;
}
}
if(col<cols-1) {
if(maze[row][col+1][4]==1)
str.append('R');
else if(maze[row][col+1][4]==0) {
list.add(row);
list.add(col+1);
maze[row][col+1][4]=2;
}
}
if(row<rows-1) {
if(maze[row+1][col][4]==1)
str.append('D');
else if(maze[row+1][col][4]==0) {
list.add(row+1);
list.add(col);
maze[row+1][col][4]=2;
}
}
//只要有候选路可走就随机选取一个方向并打通这两个迷宫单元的路径
if(str.length()!=0) {
//若候选路径数为2,则生成[0,2)区间的整数值
int random2 =new Random().nextInt(str.length());
//打通两个迷宫单元的路径
if(str.charAt(random2)=='L') {
maze[row][col][0] =1;
maze[row][--col][2] =1;
}
if(str.charAt(random2)=='U') {
maze[row][col][3] =1;
maze[--row][col][1] =1;
}
if(str.charAt(random2)=='R') {
maze[row][col][2] =1;
maze[row][++col][0] =1;
}
if(str.charAt(random2)=='D') {
maze[row][col][1] =1;
maze[++row][col][3] =1;
}
}
}
}
递归分割生成
递归分割生成算法思路为
第一,将区域划分为四个小区域,
第二,此时四个区域有四条相邻边,再将随机选取三条边进行随机连通。
第三,随机连通,即在边上随机选取一点,连通该点相邻的区域。
递归分割生成算法,略!
打印迷宫
打印迷宫,每个迷宫单元占3x5大小,x表示墙,0表示非墙。中心点默认为0,四个角默认为墙。空格为填充样式。下图是随机普里姆算法生成的迷宫
public void printMaze() {
/*
*每个迷宫单元占3x5大小,x表示墙,0表示非墙。中心点默认为0,四个角默认为墙。空格为填充样式。
* */
//遍历迷宫
for(int i=0;i<rows;i++) {
StringBuffer str1=new StringBuffer();
StringBuffer str2=new StringBuffer();
StringBuffer str3=new StringBuffer();
for(int j=0;j<cols;j++) {
//默认都为墙
char[][] str=new char[][]{{'x',' ','x',' ','x'},{'x',' ','0',' ','x'},{'x',' ','x',' ','x'}};
if(maze[i][j][0]==1)
str[1][0]='0';
if(maze[i][j][1]==1)
str[2][2]='0';
if(maze[i][j][2]==1)
str[1][4]='0';
if(maze[i][j][3]==1)
str[0][2]='0';
//保存到str1/2/3中
str1.append(str[0][0]).append(str[0][1]).append(str[0][2]).append(str[0][3]).append(str[0][4]).append(' ');
str2.append(str[1][0]).append(str[1][1]).append(str[1][2]).append(str[1][3]).append(str[1][4]).append(' ');
str3.append(str[2][0]).append(str[2][1]).append(str[2][2]).append(str[2][3]).append(str[2][4]).append(' ');
}
System.out.println(str1.toString());
System.out.println(str2.toString());
System.out.println(str3.toString());
}
}