Java深度优先遍历算法随机生成迷宫

mac2025-08-01  1

头一次写,最近刚好在做Java有关的实验,于是顺便发一篇,水平不够,有错见谅。先来展示一下最终生成迷宫的效果。行数24,列数24,左上角为入口,在下边界和右边界各有一个出口,红线和蓝线就是两条通向出口的路径。 有需要的朋友可以先来这里下载源码包:源码包

一、三种生成迷宫算法简介

迷宫算法基本上都是将迷宫划分为方格来进行的,在本文,将迷宫的最小的方格称为单元。当然,对于不同的算法,迷宫单元是非常不一样的。

1.1 Depth First Search Algorithm(深度优先遍历算法)

算法思路

从第一个单元开始,检查当前单元是否堵塞(即周围四个单元都是已被访问或不可访问)若不堵塞,则随机选择一个相邻单元作为下一单元,检查是否可访问若可访问,则打破当前单元与下一单元之间的墙壁,将当前单元入栈,将下一单元作为当前单元;若不不可访问,则回到步骤2若当前单元堵塞,则回退至上一单元如此遍历,直到所有的单元都被访问

特点

该算法产生的迷宫图片具有一条明显的主路径,比较弯曲。(看起来有点像大脑,滑稽.jpg)

1.2 Randomized Prim’s Algorithm(随机普利姆算法)

算法思路

使迷宫全是墙,即每个单元之间无法连通随机选一个单元格作为迷宫的通路,然后把它的邻墙放入列表当列表里还有墙时: 从列表里随机选一个墙,如果这面墙分隔的两个单元只有一个单元被访问过:那就从列表里移除这面墙,即把墙打通,让未访问的单元成为迷宫的通路把这个单元的墙加入列表; 如果墙两面的单元都已经被访问过,那就从列表里移除这面墙如此遍历,直到列表没有墙

特点

相对于深度优先的算法,Randomized Prim’s Algorithm不是优先选择最近选中的单元格,而是随机的从所有的列表中的单元格进行选择,新加入的单元格和旧加入的单元格同样概率会被选择,新加入的单元格没有优先权。因此其分支更多,生成的迷宫更复杂,岔路更多,难度更大,也更自然。

1.3 Recursive Division Algorithm(递归分割算法)

算法思路

让迷宫全是通路,即没有墙随机选择一偶数行和一偶数列让其全部变为墙,通过这两堵墙将整个迷宫分为四个子迷宫在3面墙上各挖一个洞(为了确保连通)如果子迷宫仍可分割成四个子迷宫,回到步骤1继续分割子迷宫如此遍历,直到所有子迷宫都不可分割。

特点

Recursive Division Algorithm十分高效,生成的迷宫较为简单,有点像四叉树,直路多且不扭曲。

二、程序整体思路

本文我们将采用第一种算法——深度优先遍历算法来生成迷宫,设定入口为左上角,在下边界和右边界各随机选择一个出口。以下是对程序分模块简要介绍。

2.1 迷宫单元模块(MazeUnit)

迷宫单元是组成迷宫图的基础,不同算法会对迷宫单元进行不同的属性设置。在这里,单元既包括四面墙,也包括方格通路。迷宫单元有四个属性,坐标(X、Y)、可访问性(isVisited)、是否被访问(accessible)、墙数组(int[])。墙的表示使用整型数组来表示,int[0]、int[1]、int[2]、int[3]的值分别表示该迷宫单元的左、上、下、右墙壁是否存在,值为0表示墙不存在,值为1表示墙存在。至于左、上、下、右是对应0、1、2、3,是为了使相对面的墙数组下标相加为3,便于计算。这样定义迷宫单元有一个缺点,就是相邻单元的墙是会重合的,所以在绘制最终的迷宫图片时,表示墙的线条时绘制两遍重叠的。

2.2 界面模块(UI)

界面模块主要功能是实现输入想要的迷宫列数和行数,同时对于输入的数字进行一定的过滤,例如:当行列数过大时报错提醒,因为行列数过大电脑屏幕装不下一张迷宫图等)

2.3 堆栈模块(PathStack)

堆栈的主要作用是在遍历时记录主路径。在遍历时,将每次走过的迷宫单元放入堆栈中。当遍历时遇到当前迷宫单元堵塞的情况时,就弹出栈顶的迷宫单元,实现返回上一迷宫单元。 堆栈的另一个作用就是当遍历经过出口的坐标时,可以复制当前堆栈储存来保存入口到出口的路径。根据算法来理解一下,其实很容易明白:该路径便是入口到出口的最短路径。

2.4 主模块(CreateMaze)

主模块便是生成迷宫算法实现的核心了。主要流程如下:

初始化迷宫数据:创建二维数组MazeUnit[][];将所有的墙设置为存在;将所有的单元设置为未访问的;设置外围边界不可访问(为防止数组越界,定义MazeUnit[][]时比实际的迷宫向外拓展了一层,这里称为外围边界)。生成入口和出口:将入口的墙打破(即定义该单元的其中某一面墙不存在),同时随机在整个迷宫的下边界和右边界分别选择一个出口并将墙打破。定义用于遍历的辅助方法:randway()用于随机选择下一个相邻单元;breakwall()用于将当前单元与下一个相邻单元之间的墙壁打破;isBlock用于判断当前单元是否堵塞(即四个相邻单元都是已访问或者不可访问);countIsVisited()用于统计当前已经访问的单元格数。对整个迷宫进行遍历:从入口(第一个单元)开始,检查当前单元是否堵塞,若不堵塞,则随机选择一个相邻单元作为下一单元,检查是否可访问。若可访问,则打破当前单元与下一单元之间的墙壁,将当前单元入栈,将下一单元作为当前单元;若不不可访问,则再次随机选择一个相邻单元。若当前单元堵塞,则回退至上一单元。如此遍历,直到所有的单元都被访问。记录两条路径:当遍历时,当前单元为随机选择的出口时,将当前堆栈复制并储存,记录下两个出口的路径。遍历完成之后,调用了drawMaze类,在该类中重写了paint方法,将遍历后的迷宫数组数据,转化为坐标,绘制成图,并将两个出口的路径也表示出来。

三、程序实现

3.1 开发平台

开发平台:Eclipse

3.2 主要代码

MazeUnit.java 迷宫单元类

package maze; public class MazeUnit { public boolean isVisited;//是否已经被访问 public int wall[];//0、1、2、3分别代表左、上、下、右 public boolean accessible;//可访问性 int x,y;//坐标 //构造方法 MazeUnit(boolean gisVisited, int[] gwall, boolean gaccessible){ isVisited = gisVisited; wall = gwall; accessible = gaccessible; } }

CreateMaze.java 生成迷宫的主要代码

1.主要方法
public static int x,y,nx,ny,dir;//x、y为当前单元坐标,nx、ny为相邻单元的坐标,dir为当前单元的墙的方向 public static void initialize(int mx,int my) { MazeUnit[][] pos = new MazeUnit[mx][my];//mx、my为行数、列数加2 //初始化迷宫单元数组 int i,j; for(i=0;i<mx;i++) { for(j=0;j<my;j++) { int[] wall = new int[4]; for(int w=0;w<4;w++) { wall[w] = 1;//将所有迷宫单元用墙隔开 } pos[i][j] = new MazeUnit(false,wall,true); pos[i][j].x = i; pos[i][j].y = j; } } //设置可访问性数组的值,创建外围边界 for(i=1;i<mx-1;i++) { for(j=1;j<my-1;j++) { pos[i][j].accessible = true; } } i=0; for(j=0;j<my;j++) { pos[i][j].accessible = false; } i=mx-1; for(j=0;j<my;j++) { pos[i][j].accessible = false; } j=0; for(i=0;i<mx;i++) { pos[i][j].accessible = false; } j=my-1; for(i=0;i<mx;i++) { pos[i][j].accessible = false; } //生成入口和两个随机出口 Random randx = new Random(); int endx = randx.nextInt(mx-2)+1; Random randy = new Random(); int endy = randy.nextInt(my-2)+1; pos[1][1].wall[0] = 0; pos[endx][my-2].wall[2] = 0; pos[mx-2][endy].wall[3] = 0; IArrayStack path = new StackImpl(mx-2,my-2); path.push(pos[1][1]); x=y=1; nx=ny=0; dir=0; Object[] pathx = new Object[(mx-2)*(my-2)]; Object[] pathy = new Object[(mx-2)*(my-2)]; while(countIsVisited(pos,mx,my) < (mx-2)*(my-2)) { randway(); //记录两个出口的路径 if((x == endx)&&(y == my-2)) { pathx = path.copyArray().clone(); } if((x == mx-2)&&(y == endy)) { pathy = path.copyArray().clone(); } if(isBlock(pos,x,y) == false) { breakwall(pos,path); } else if(isBlock(pos,x,y) == true) { pos[x][y].isVisited = true; path.pop();//将当前单元出栈 int[] tempwall = new int[4]; MazeUnit temp = new MazeUnit(false,tempwall,true); temp = (MazeUnit) path.top();//将前一单元转换为当前单元 x = temp.x; y = temp.y;//将坐标转换为前一单元的坐标 } } JFrame win2 = new JFrame(); win2.setTitle("随机生成迷宫"); win2.setSize(mx*30+80, my*30+80); win2.setLocationRelativeTo(null); win2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //绘制迷宫的图片 drawMaze pic = new drawMaze(mx,my); pic.getData(pos,pathx,pathy,mx,my); win2.add(pic, BorderLayout.CENTER); win2.setResizable(false); win2.setVisible(true); }
randway()
//随机选择一个相邻单元 private static void randway() { Random rand = new Random(); dir = rand.nextInt(4); switch(dir) { case 0: nx = x-1; ny = y; break; case 1: nx = x; ny = y-1; break; case 2: nx = x; ny = y+1; break; case 3: nx = x+1; ny = y; break; } }
breakwall()
//当前迷宫单元随机选择一个相邻的单元进行破墙 private static void breakwall(MazeUnit[][] pos,IArrayStack path) { if((pos[nx][ny].accessible == true) && (pos[nx][ny].isVisited == false)){ pos[x][y].wall[dir] = 0; pos[x][y].isVisited = true; pos[nx][ny].wall[3-dir] = 0; path.push(pos[nx][ny]); x = nx; y = ny; } else { randway(); breakwall(pos,path); } }
isBlock()
//判断当前迷宫单元是否堵塞 private static boolean isBlock(MazeUnit[][] pos1,int x,int y) { int block=0; for(int w=0;w<4;w++) { int nx=0,ny=0; switch(w) { case 0: nx = x-1; ny = y; break; case 1: nx = x; ny = y-1; break; case 2: nx = x; ny = y+1; break; case 3: nx = x+1; ny = y; break; } if((pos1[nx][ny].accessible == false) || (pos1[nx][ny].isVisited == true)) { block++; } } if(block == 4) return true; else return false; }
countIsVisited()
//计算已访问的单元个数 private static int countIsVisited(MazeUnit[][] pos3,int mx,int my) { int num = 0; for(int i=1;i<mx;i++) { for(int j=1;j<my;j++) { if(pos3[i][j].isVisited == true) { num++; } } } return num; }

四、总结

整个算法还算是比较容易理解的,实际上就是把整个迷宫的单元随机访问一遍,访问迷宫单元实际上就是把该单元接入通路之中,也就是说被访问过的任意两个单元之间是一定有一条通路的。按照这个想法,其实不管迷宫是要多少个出口和入口,都是可以实现的。由于我对Java还是有点生疏,这个程序代码总的来说还是不够简洁的,不够思路可以供大家参考一下。

头一次写,希望对大家学习Java或者了解迷宫算法有点帮助。需要源码的朋友可以在这里下载:源码包

最新回复(0)