迷宫的生成——深度优先搜索

版权声明:本文为博主原创文章,未经博主允许不得转载。 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的迷宫(尺寸太大会看不清图片的)

猜你喜欢

转载自blog.csdn.net/Kurozaki_Kun/article/details/81637918