版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Kurozaki_Kun/article/details/81637918
生成一个迷宫的算法有好几种,下面来介绍一下用深度优先搜索的思想来生成一个迷宫。
算法描述
迷宫的初始状态是一张有众多单元格组成的网格,单元格的初始状态是“四面有墙”,DFS的步骤如下
1.将起点作为当前迷宫单元并标记为已访问
2.当还存在未标记的迷宫单元,进行循环
1.如果当前迷宫单元有未被访问过的的相邻的迷宫单元
1.随机选择一个未访问的相邻迷宫单元
2.将当前迷宫单元入栈
3.移除当前迷宫单元与相邻迷宫单元的墙
4.标记相邻迷宫单元并用它作为当前迷宫单元
2.如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空
1.栈顶的迷宫单元出栈
2.令其成为当前迷宫单元
代码实现(Java)
package core;
import java.util.*;
/**
* Created by YotWei on 2018/8/13.
*/
public class Maze2dGenerator {
private static final int LEFT = 1 /* 1 << 0*/, RIGHT = 1 << 1, UP = 1 << 2, DOWN = 1 << 3;
public Maze generate(int width, int height) {
/**
* 一个单元格的四周状态可以应该由 4 个 boolean 型表示上下左右是“有墙”还是“无墙”
*
* 但是这里对这几个 boolean 标识压缩进一个 byte 表示,
* byte 的低四位依次表示四面是否有墙,0表示有墙,1表示无墙,请参考上面几个常量的定义
* 下面这个 byte[][] 数组实质上用来表示所有单元格四周的状况
*/
byte[][] mazeCells = new byte[height][width];
Random rnd = new Random(); // 产生随机数用
Stack<Point> ss = new Stack<>(); // 搜索栈
// 随机选取初始搜索点
ss.push(new Point(rnd.nextInt(width), rnd.nextInt(height)));
while (!ss.isEmpty()) {
Point p = ss.peek();
Point[] adjCells = new Point[]{
new Point(p.x - 1, p.y), // left
new Point(p.x + 1, p.y), // right
new Point(p.x, p.y - 1), // up
new Point(p.x, p.y + 1) // down
};
Map<Byte, Point> searchAbles = new HashMap<>();
for (int di = 0; di < adjCells.length; di++) {
Point ap = adjCells[di];
if (ap.inbound(width, height) && mazeCells[ap.y][ap.x] == 0) {
searchAbles.put((byte) (1 << di), ap);
}
}
if (searchAbles.isEmpty()) {
ss.pop();
continue;
}
int k = rnd.nextInt(searchAbles.size()); // 随机选取一个可搜索的相邻单元格
for (Byte dir : searchAbles.keySet()) {
if (k == 0) {
/*
* 如果要消除 A B 单元格之间的墙
* 对 A 来说需要消除右面
* 对 B 来说需要消除左面
* 相邻的两个单元格要消除的墙面方向恰好相反
*
* +---+---+
* | A B |
* +---+---+
* | | |
* +---+---+
*/
mazeCells[p.y][p.x] |= dir;
Point sp = searchAbles.get(dir);
switch (dir) {
case UP:
mazeCells[sp.y][sp.x] |= DOWN;
break;
case DOWN:
mazeCells[sp.y][sp.x] |= UP;
break;
case LEFT:
mazeCells[sp.y][sp.x] |= RIGHT;
break;
case RIGHT:
mazeCells[sp.y][sp.x] |= LEFT;
break;
}
ss.push(sp);
break;
}
k--;
}
}
// Maze 是一个迷宫对象,关于 Maze 类的定义往下看
return new Maze(width, height, mazeCells);
}
}
package core;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* Created by YotWei on 2018/8/13.
*/
public class Maze {
private static final int LEFT = 1 /* 1 << 0*/, RIGHT = 1 << 1, UP = 1 << 2, DOWN = 1 << 3;
private byte[][] cells;
private int width, height;
private Point startPoint, endPoint;
Maze(int width, int height, byte[][] cells) {
this.width = width;
this.height = height;
this.cells = cells;
this.startPoint = new Point(0, 0);
this.endPoint = new Point(width - 1, height - 1);
}
public void setStartPoint(int x, int y) {
this.startPoint.x = x;
this.startPoint.y = y;
}
public void setEndPoint(int x, int y) {
this.endPoint.x = x;
this.endPoint.y = y;
}
/**
* 这是一个根据迷宫生成图片的函数,写的比较粗糙
*
* 可以根据自己的喜好对绘制的方式做一定的变化
*/
public BufferedImage getImage() {
Dimension unitSize = new Dimension(8, 8);
BufferedImage image = new BufferedImage(
width * unitSize.width + 1,
height * unitSize.height + 1,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) image.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, image.getWidth(), image.getHeight());
g.setColor(Color.black);
for (int y = 0; y < cells.length; y++) {
for (int x = 0; x < cells[y].length; x++) {
byte c = cells[y][x];
if ((c & UP) == 0) {
g.drawLine(x * unitSize.width,
y * unitSize.height,
(x + 1) * unitSize.width,
y * unitSize.height);
}
if ((c & DOWN) == 0) {
g.drawLine(x * unitSize.width,
(y + 1) * unitSize.height,
(x + 1) * unitSize.width,
(y + 1) * unitSize.height);
}
if ((c & LEFT) == 0) {
g.drawLine(x * unitSize.width,
y * unitSize.height,
x * unitSize.width,
(y + 1) * unitSize.height);
}
if ((c & RIGHT) == 0) {
g.drawLine((x + 1) * unitSize.width,
y * unitSize.height,
(x + 1) * unitSize.width,
(y + 1) * unitSize.height);
}
}
}
g.setColor(Color.red);
g.fillRect(startPoint.x * unitSize.width + 2, startPoint.y * unitSize.height + 2,
unitSize.width - 3, unitSize.height - 3);
g.setColor(Color.blue);
g.fillRect(endPoint.x * unitSize.width + 2, endPoint.y * unitSize.height + 2,
unitSize.width - 3, unitSize.height - 3);
return image;
}
}
最后是客户端
/**
* Created by YotWei on 2018/8/13.
*/
public class Client {
public static void main(String[] args) throws Exception {
Maze2dGenerator g = new Maze2dGenerator();
Maze maze = g.generate(128, 64); // 生成一个128x64的迷宫
// maze.setStartPoint(0, 12);
// maze.setEndPoint(127, 9);
BufferedImage image = maze.getImage(); // 绘制迷宫的图像,然后输出到文件
ImageIO.write(image, "png", new File("./maze.png"));
}
}
环路与孤立区域问题
有一些迷宫会出现两种情况
1、环路
2、孤立区域
扫描二维码关注公众号,回复:
3107879 查看本文章
深度优先搜索是完全可以避免环路与“孤立区域”的。图论中生成树的算法也可以采用深度优先搜索,这里产生的迷宫本质上也可以看作是一棵生成树。
生成树有几条重要的性质
1、包含所有节点,迷宫中所有的单元格都会被搜索
2、树不存在环,所以迷宫也不会产生环路
3、树是连通图,因此用DFS产生的迷宫不会产生“孤立区域”
4、任意两节点都有通路,在迷宫中起点终点随意摆放都会有解
关于效率
生成1024x1024的迷宫花费1s左右
生成2048x2048花费5s左右
生成4096x4096花费14s左右
8192x8192没测,而且迷宫尺寸太大还有爆栈的可能性,上面统计的只是生成迷宫花费的时间,如果加上绘图这个时间要翻好几倍
下面是一张256x256的迷宫(尺寸太大会看不清图片的)