迷宫算法之迷宫生成和迷宫寻路算法


三种迷宫生成算法

  1. DFS(即深度优先)算法生成,分为递归和非递归方法
  2. 十字分割算法生成,分为递归和非递归方法
  3. 随机 Prim 算法生成,一种非递归方法

两种迷宫寻路算法

  1. DFS 寻路,本文采用非递归实现
  2. A* 寻路,一种非递归方法

一些说明

  1. 代码实现语言:C++
  2. 环境:Win10 + VS2019
  3. 迷宫同一要求:长宽均为奇数 N,最外围一圈是墙,入口坐标(0, 1),出口坐标(N-1, N-2)
  4. 由 EasyX 制作的迷宫算法可视化程序:https://codebus.cn/teternity/post/MazeAlgorithm

三种迷宫生成算法

最外围是墙或入口,因此操作范围是:(1, 1) 到 (N-2, N-2)

DFS 算法生成(一种挖墙算法)

  1. 初始时全是墙
  2. 迷宫算法java 迷宫算法论文_递归

  3. x,y 均在 1~N-2 中的奇数随机选取一点(均为奇数),将其挖开,并将该点入栈
  4. 迷宫算法java 迷宫算法论文_迷宫算法java_02

  5. 四个方向随机选取一个方向,设当前挖开坐标为(x, y),若该方向上满足 (x + dx*2, y + dy*2) 是墙(dx 和 dy 代表方向,取值为 1 或 -1),则挖开 (x + dx, y + dy),并重设当前点为 (x + dx*2, y + dy*2),将当前点入栈
  6. 以 Cur 为当前点,重复操作步骤 2
  7. 迷宫算法java 迷宫算法论文_迷宫算法java_03

  8. 若 Cur 不能挖开周围的墙,则栈执行 pop 操作,并将 Cur 重置为此时栈中的 top 元素
  9. 迷宫算法java 迷宫算法论文_#include_04

    迷宫算法java 迷宫算法论文_迷宫算法java_05

    迷宫算法java 迷宫算法论文_迭代_06

  10. 直到栈为空,说明挖墙操作结束

生成形态:

迷宫算法java 迷宫算法论文_递归_07

源码(包含迭代和非迭代版,注释 EasyX 的地方是用 EasyX 绘图):

#include <iostream>
#include <ctime>
#include <stack>
#include <vector>
#include <algorithm>
#include <easyx.h>
using namespace std;


// 迷宫格子状态
enum CellState:int { PATH = 0, WALL, FLAG };

// 迷宫格二维点结构
struct Point2
{
    int x, y;
    Point2(int _x, int _y) :x(_x), y(_y) {}
};

// 迷宫大小(要求为奇数)
const int mazeSize = 21;


// 迷宫生成接口--递归版
void DFS_generator(int _x, int _y, std::vector<std::vector<int>>& maze)
{
    // 定义方向容器
    std::vector<std::vector<int>> dir{ {1,0},{-1,0},{0,1},{0,-1} };
    // 随机打乱方向
    std::random_shuffle(dir.begin(), dir.end());
    // 递归生成迷宫
    maze[_x][_y] = PATH;
    for (int i = 0; i < 4; ++i)
    {
        if (_x + 2 * dir[i][0] >= 1 && _x + 2 * dir[i][0] <= mazeSize - 2 && _y + 2 * dir[i][1] >= 1 && _y + 2 * dir[i][1] <= mazeSize - 2
            && maze[_x + 2 * dir[i][0]][_y + 2 * dir[i][1]] == WALL)
        {
            maze[_x + dir[i][0]][_y + dir[i][1]] = PATH;
            DFS_generator(_x + 2 * dir[i][0], _y + 2 * dir[i][1], maze);
        }
    }
}

// 迷宫生成接口--迭代版
void DFS_iterative_generator(std::vector<std::vector<int>>& maze)
{
    // 定义栈容器
    std::stack<Point2> sp;
    // 定义方向容器
    std::vector<std::vector<int>> dir{ {1,0},{-1,0},{0,1},{0,-1} };
    // 要求参数为奇数
    Point2 temp((rand() % (mazeSize - 2) + 1) | 1, (rand() % (mazeSize - 2) + 1) | 1);
    sp.push(temp);
    // 后续迭代生成迷宫,并绘制
    while (!sp.empty())
    {
        if (maze[temp.x][temp.y] != PATH)
            maze[temp.x][temp.y] = PATH;
        // 随机打乱方向
        std::random_shuffle(dir.begin(), dir.end());
        int i = 0;
        for (; i < 4; ++i)
        {
            if (temp.x + 2 * dir[i][0] >= 1 && temp.x + 2 * dir[i][0] <= mazeSize - 2 && temp.y + 2 * dir[i][1] >= 1 && temp.y + 2 * dir[i][1] <= mazeSize - 2
                && maze[temp.x + 2 * dir[i][0]][temp.y + 2 * dir[i][1]] == WALL)
            {
                maze[temp.x + dir[i][0]][temp.y + dir[i][1]] = PATH;
                temp.x += 2 * dir[i][0];
                temp.y += 2 * dir[i][1];
                sp.push(temp);
                break;
            }
        }
        if (i == 4) sp.pop();
        if (!sp.empty()) temp = sp.top();
    }
}


// main 函数
int main()
{
    srand((unsigned)time(nullptr));

    // 入口出口
    Point2 start(0, 1);
    Point2 end(mazeSize - 1, mazeSize - 2);

    // 二维迷宫容器
    std::vector<std::vector<int>> maze;

    // 初始化迷宫
    for (int i = 0; i < mazeSize; ++i) maze.push_back(std::vector<int>());
    for (int i = 0; i < mazeSize; ++i)
        for (int j = 0; j < mazeSize; ++j)
            maze[i].push_back(WALL);
    maze[start.x][start.y] = maze[end.x][end.y] = PATH;

    // 生成迷宫(迭代和非迭代二选一生成)
    DFS_generator((rand() % (mazeSize - 2) + 1) | 1, (rand() % (mazeSize - 2) + 1) | 1, maze);
    // DFS_iterative_generator(maze);

    // 打印迷宫
    for (int j = 0; j < mazeSize; ++j)
    {
        for (int i = 0; i < mazeSize; ++i)
            cout << maze[i][j] << " ";
        cout << endl;
    }

    // EasyX
    {
        auto ret = _getwch();
        const int width = 15;
        initgraph(mazeSize * width, mazeSize * width);
        setlinecolor(DARKGRAY);
        setfillcolor(LIGHTGRAY);
        for (int j = 0; j < mazeSize; ++j)
            for (int i = 0; i < mazeSize; ++i)
                if (maze[i][j] == WALL)
                    fillrectangle(i * width, j * width, i * width + width - 1, j * width + width - 1);
        // saveimage(_T("D:\\maze.png"));
        ret = _getwch();
        closegraph();
    }

    return 0;
}


十字分割算法生成(是一种十字补墙算法)

  1. 初始时除了四周全通路
  2. 迷宫算法java 迷宫算法论文_#include_08

  3. x,y 均在 1~N-2 中随机选取一点(均为偶数),然后十字建墙
  4. 迷宫算法java 迷宫算法论文_迷宫算法java_09

  5. 在建好的四面墙(不包含选取点)中随机选择三面,找奇数点开洞,使得四个子空间连通
  6. 迷宫算法java 迷宫算法论文_迭代_10

  7. 对四个子空间重复操作,直到子空间不可分割为止(淡黄色是开洞位置)(何时不可分割?长度 或 宽度 不大于 1 时)
  8. 迷宫算法java 迷宫算法论文_递归_11

生成形态:

迷宫算法java 迷宫算法论文_迷宫算法java_12

源码(包含迭代和非迭代版,注释 EasyX 的地方是用 EasyX 绘图):

#include <iostream>
#include <ctime>
#include <stack>
#include <vector>
#include <algorithm>
#include <easyx.h>
using namespace std;


// 迷宫格子状态
enum CellState:int { PATH = 0, WALL, FLAG };

// 迷宫格二维点结构
struct Point2
{
    int x, y;
    Point2(int _x, int _y) :x(_x), y(_y) {}
};

// 四维点,用于分割矩形区间
struct Point4
{
    int x1, x2;
    int y1, y2;
    Point4(int _x1, int _x2, int _y1, int _y2) :x1(_x1), x2(_x2), y1(_y1), y2(_y2) {}
};

// 迷宫大小(要求为奇数)
const int mazeSize = 21;


// 迷宫生成接口--递归版
void Division_generator(int _l, int _r, int _t, int _b, std::vector<std::vector<int>>& maze)
{
    // 间隔大于 1 时可分割
    if (_r - _l > 1 && _b - _t > 1)
    {
        int i = 0;
        // 要求分割点 px,py 为偶数
        int px = ((rand() % (_r - _l) + _l + 1) | 1) - 1;
        int py = ((rand() % (_b - _t) + _t + 1) | 1) - 1;
        while (px + i <= _r || px - i >= _l || py + i <= _b || py - i >= _t)
        {
            if (px + i <= _r) maze[px + i][py] = WALL;
            if (px - i >= _l) maze[px - i][py] = WALL;
            if (py + i <= _b) maze[px][py + i] = WALL;
            if (py - i >= _t) maze[px][py - i] = WALL;
            ++i;
        }
        // 定义方向容器,随机在三面墙上开洞
        // 要求开洞位置是奇数
        std::vector<int> dir{ 0,1,2,3 };
        std::random_shuffle(dir.begin(), dir.end());
        for (int i = 0; i < 3; ++i)
        {
            if (dir[i] == 0)
            {
                int xx = (rand() % (px - _l) + _l) | 1;
                maze[xx][py] = PATH;
            }
            else if (dir[i] == 1)
            {
                int xx = (rand() % (_r - px) + px) | 1;
                maze[xx][py] = PATH;
            }
            else if (dir[i] == 2)
            {
                int yy = (rand() % (py - _t) + _t) | 1;
                maze[px][yy] = PATH;
            }
            else if (dir[i] == 3)
            {
                int yy = (rand() % (_b - py) + py) | 1;
                maze[px][yy] = PATH;
            }
        }
        // 递归分割
        Division_generator(_l, px - 1, _t, py - 1, maze);
        Division_generator(px + 1, _r, _t, py - 1, maze);
        Division_generator(_l, px - 1, py + 1, _b, maze);
        Division_generator(px + 1, _r, py + 1, _b, maze);
    }
}

// 迷宫生成接口--迭代版
void Division_iterative_generator(std::vector<std::vector<int>>& maze)
{
    // 定义栈容器
    std::stack<Point4> sp;
    // 定义方向容器
    std::vector<int> dir{ 0,1,2,3 };
    // 要求参数为奇数
    Point4 temp(1, mazeSize - 2, 1, mazeSize - 2);
    sp.push(temp);
    // 后续迭代生成迷宫
    while (!sp.empty())
    {
        sp.pop();
        if (temp.x2 - temp.x1 > 1 && temp.y2 - temp.y1 > 1)
        {
            int i = 0;
            int px = ((rand() % (temp.x2 - temp.x1) + temp.x1 + 1) | 1) - 1;
            int py = ((rand() % (temp.y2 - temp.y1) + temp.y1 + 1) | 1) - 1;
            while (px + i <= temp.x2 || px - i >= temp.x1 || py + i <= temp.y2 || py - i >= temp.y1)
            {
                if (px + i <= temp.x2) maze[px + i][py] = WALL;
                if (px - i >= temp.x1) maze[px - i][py] = WALL;
                if (py + i <= temp.y2) maze[px][py + i] = WALL;
                if (py - i >= temp.y1) maze[px][py - i] = WALL;
                ++i;
            }
            // 随机在三面墙上开洞,要求开洞位置是奇数
            std::random_shuffle(dir.begin(), dir.end());
            for (int i = 0; i < 3; ++i)
            {
                if (dir[i] == 0)
                {
                    int xx = (rand() % (px - temp.x1) + temp.x1) | 1;
                    maze[xx][py] = PATH;
                }
                else if (dir[i] == 1)
                {
                    int xx = (rand() % (temp.x2 - px) + px) | 1;
                    maze[xx][py] = PATH;
                }
                else if (dir[i] == 2)
                {
                    int yy = (rand() % (py - temp.y1) + temp.y1) | 1;
                    maze[px][yy] = PATH;
                }
                else if (dir[i] == 3)
                {
                    int yy = (rand() % (temp.y2 - py) + py) | 1;
                    maze[px][yy] = PATH;
                }
            }
            // 将三个方块区间入栈
            sp.push(Point4(px + 1, temp.x2, py + 1, temp.y2));
            sp.push(Point4(temp.x1, px - 1, py + 1, temp.y2));
            sp.push(Point4(px + 1, temp.x2, temp.y1, py - 1));
            temp.x2 = px - 1;
            temp.y2 = py - 1;
            sp.push(temp);
        }
        else if (!sp.empty()) { temp = sp.top(); }
    }
}


// main 函数
int main()
{
    srand((unsigned)time(nullptr));

    // 入口出口
    Point2 start(0, 1);
    Point2 end(mazeSize - 1, mazeSize - 2);

    // 二维迷宫容器
    std::vector<std::vector<int>> maze;

    // 初始化迷宫
    for (int i = 0; i < mazeSize; ++i) maze.push_back(std::vector<int>());
    for (int i = 0; i < mazeSize; ++i)
        for (int j = 0; j < mazeSize; ++j)
            (i == 0 || j == 0 || i == mazeSize - 1 || j == mazeSize - 1) ? maze[i].push_back(WALL) : maze[i].push_back(PATH);
    maze[start.x][start.y] = maze[end.x][end.y] = PATH;

    // 生成迷宫(迭代和非迭代二选一生成)
    Division_generator(1, mazeSize - 2, 1, mazeSize - 2, maze);
    // Division_iterative_generator(maze);

    // 打印迷宫
    for (int j = 0; j < mazeSize; ++j)
    {
        for (int i = 0; i < mazeSize; ++i)
            cout << maze[i][j] << " ";
        cout << endl;
    }

    // EasyX
    {
        auto ret = _getwch();
        const int width = 15;
        initgraph(mazeSize * width, mazeSize * width);
        setlinecolor(DARKGRAY);
        setfillcolor(LIGHTGRAY);
        for (int j = 0; j < mazeSize; ++j)
            for (int i = 0; i < mazeSize; ++i)
                if (maze[i][j] == WALL)
                    fillrectangle(i * width, j * width, i * width + width - 1, j * width + width - 1);
        // saveimage(_T("D:\\maze.png"));
        ret = _getwch();
        closegraph();
    }

    return 0;
}


随机 Prim 算法生成(一种非递归方法)

  1. 初始时全是墙
  2. 迷宫算法java 迷宫算法论文_迷宫算法java_13

  3. 构建一墙一通路形式
  4. 迷宫算法java 迷宫算法论文_递归_14

  5. 随机选择一个通路,并将周围墙入容器,标记该通路
  6. 迷宫算法java 迷宫算法论文_递归_15

  7. 在墙容器中随机选取一堵墙,如果墙两边的通路没有同时被标记,则打通该墙,并将原来未被标记的通路周围的墙加入容器,然后将该通路标记,最后移除该墙
  8. 迷宫算法java 迷宫算法论文_递归_16

    迷宫算法java 迷宫算法论文_#include_17

  9. 重复操作 4,直到墙容器为空,说明该算法完成,最后将被标记的通路清除标记

生成形态:

迷宫算法java 迷宫算法论文_递归_18

源码(注释 EasyX 的地方是用 EasyX 绘图):

#include <iostream>
#include <ctime>
#include <stack>
#include <vector>
#include <algorithm>
#include <easyx.h>
using namespace std;


// 迷宫格子状态
enum CellState:int { PATH = 0, WALL, FLAG };

// 迷宫格二维点结构
struct Point2
{
    int x, y;
    Point2(int _x, int _y) :x(_x), y(_y) {}
};

// 迷宫大小(要求为奇数)
const int mazeSize = 21;


// 迷宫生成接口
void Prim_generator(std::vector<std::vector<int>>& maze)
{
    // 构建墙隔开通路的迷宫,奇数点为通路
    for (int i = 1; i <= mazeSize - 2; i += 2)
        for (int j = 1; j <= mazeSize - 2; j += 2)
            maze[i][j] = PATH;
    // 维护一个墙容器
    std::vector<Point2> vp;
    // 先随机找一个通路
    Point2 temp((rand() % (mazeSize - 2) + 1) | 1, (rand() % (mazeSize - 2) + 1) | 1);
    // 将周围墙入栈
    if (temp.x - 1 >= 2) vp.push_back(Point2(temp.x - 1, temp.y));
    if (temp.x + 1 <= mazeSize - 3) vp.push_back(Point2(temp.x + 1, temp.y));
    if (temp.y - 1 >= 2) vp.push_back(Point2(temp.x, temp.y - 1));
    if (temp.y + 1 <= mazeSize - 3) vp.push_back(Point2(temp.x, temp.y + 1));
    // 标记该通路
    maze[temp.x][temp.y] = FLAG;
    int pos = 0;
    // 后续迭代生成迷宫
    while (!vp.empty())
    {
        // 在墙容器中随机选取一堵墙
        pos = rand() % vp.size();
        temp = vp[pos];
        // 记录该墙是否打通
        bool flag = false;
        // 后续 if else 判断墙所隔离通路在左右还是上下,并判断是否打通
        if (maze[temp.x + 1][temp.y] == WALL)
        {
            if (maze[temp.x][temp.y - 1] != maze[temp.x][temp.y + 1])
            {
                maze[temp.x][temp.y] = PATH;
                // 对新加入的通路进行标记
                if (maze[temp.x][temp.y - 1] == FLAG) { maze[temp.x][temp.y + 1] = FLAG; ++temp.y; }
                else { maze[temp.x][temp.y - 1] = FLAG; --temp.y; }
                flag = true;
            }
        }
        else
        {
            if (maze[temp.x - 1][temp.y] != maze[temp.x + 1][temp.y])
            {
                maze[temp.x][temp.y] = PATH;
                // 对新加入的通路进行标记
                if (maze[temp.x - 1][temp.y] == FLAG) { maze[temp.x + 1][temp.y] = FLAG; ++temp.x; }
                else { maze[temp.x - 1][temp.y] = FLAG; --temp.x; }
                flag = true;
            }
        }
        // 如果打通了墙,将进加入的通路周围的墙入容器
        if (flag)
        {
            if (temp.x - 1 >= 2 && maze[temp.x - 1][temp.y] == WALL) vp.push_back(Point2(temp.x - 1, temp.y));
            if (temp.x + 1 <= mazeSize - 3 && maze[temp.x + 1][temp.y] == WALL) vp.push_back(Point2(temp.x + 1, temp.y));
            if (temp.y - 1 >= 2 && maze[temp.x][temp.y - 1] == WALL) vp.push_back(Point2(temp.x, temp.y - 1));
            if (temp.y + 1 <= mazeSize - 3 && maze[temp.x][temp.y + 1] == WALL) vp.push_back(Point2(temp.x, temp.y + 1));
        }
        // 移除该墙
        vp[pos] = *(vp.end() - 1);
        vp.pop_back();
    }
    // 将被标记的通路还原
    for (auto& v1 : maze)
        for (auto& v2 : v1)
            if (v2 == FLAG) v2 = PATH;
}


// main 函数
int main()
{
    srand((unsigned)time(nullptr));

    // 入口出口
    Point2 start(0, 1);
    Point2 end(mazeSize - 1, mazeSize - 2);

    // 二维迷宫容器
    std::vector<std::vector<int>> maze;

    // 初始化迷宫
    for (int i = 0; i < mazeSize; ++i) maze.push_back(std::vector<int>());
    for (int i = 0; i < mazeSize; ++i)
        for (int j = 0; j < mazeSize; ++j)
            maze[i].push_back(WALL);
    maze[start.x][start.y] = maze[end.x][end.y] = PATH;

    // 生成迷宫
    Prim_generator(maze);

    // 打印迷宫
    for (int j = 0; j < mazeSize; ++j)
    {
        for (int i = 0; i < mazeSize; ++i)
            cout << maze[i][j] << " ";
        cout << endl;
    }

    // EasyX
    {
        auto ret = _getwch();
        const int width = 15;
        initgraph(mazeSize * width, mazeSize * width);
        setlinecolor(DARKGRAY);
        setfillcolor(LIGHTGRAY);
        for (int j = 0; j < mazeSize; ++j)
            for (int i = 0; i < mazeSize; ++i)
                if (maze[i][j] == WALL)
                    fillrectangle(i * width, j * width, i * width + width - 1, j * width + width - 1);
        // saveimage(_T("D:\\maze.png"));
        ret = _getwch();
        closegraph();
    }

    return 0;
}


两种迷宫寻路算法

DFS 寻路,采用非递归实现

该寻路方法,应该算是比较简单算法,学习数据结构时一般就会接触该算法

从起点开始,将当前点入栈,向某一方向前进。若周围方向均不能继续前进,则回退,直到找到终点或栈为空,就不再贴图

生成形态:

迷宫算法java 迷宫算法论文_迷宫算法java_19

源码(注释 EasyX 的地方是用 EasyX 绘图):

#include <iostream>
#include <ctime>
#include <list>
#include <stack>
#include <vector>
#include <algorithm>
#include <easyx.h>
using namespace std;


// 迷宫格子状态
enum CellState:int { PATH = 0, WALL, FLAG };

// 迷宫格二维点结构
struct Point2
{
    int x, y;
    Point2(int _x, int _y) :x(_x), y(_y) {}
    bool operator == (const Point2& p2) { return x == p2.x && y == p2.y; }
};

// 迷宫大小(要求为奇数)
const int mazeSize = 21;


// 迷宫生成接口
void Prim_generator(std::vector<std::vector<int>>& maze)
{
    // 构建墙隔开通路的迷宫,奇数点为通路
    for (int i = 1; i <= mazeSize - 2; i += 2)
        for (int j = 1; j <= mazeSize - 2; j += 2)
            maze[i][j] = PATH;
    // 维护一个墙容器
    std::vector<Point2> vp;
    // 先随机找一个通路
    Point2 temp((rand() % (mazeSize - 2) + 1) | 1, (rand() % (mazeSize - 2) + 1) | 1);
    // 将周围墙入栈
    if (temp.x - 1 >= 2) vp.push_back(Point2(temp.x - 1, temp.y));
    if (temp.x + 1 <= mazeSize - 3) vp.push_back(Point2(temp.x + 1, temp.y));
    if (temp.y - 1 >= 2) vp.push_back(Point2(temp.x, temp.y - 1));
    if (temp.y + 1 <= mazeSize - 3) vp.push_back(Point2(temp.x, temp.y + 1));
    // 标记该通路
    maze[temp.x][temp.y] = FLAG;
    int pos = 0;
    // 后续迭代生成迷宫
    while (!vp.empty())
    {
        // 在墙容器中随机选取一堵墙
        pos = rand() % vp.size();
        temp = vp[pos];
        // 记录该墙是否打通
        bool flag = false;
        // 后续 if else 判断墙所隔离通路在左右还是上下,并判断是否打通
        if (maze[temp.x + 1][temp.y] == WALL)
        {
            if (maze[temp.x][temp.y - 1] != maze[temp.x][temp.y + 1])
            {
                maze[temp.x][temp.y] = PATH;
                // 对新加入的通路进行标记
                if (maze[temp.x][temp.y - 1] == FLAG) { maze[temp.x][temp.y + 1] = FLAG; ++temp.y; }
                else { maze[temp.x][temp.y - 1] = FLAG; --temp.y; }
                flag = true;
            }
        }
        else
        {
            if (maze[temp.x - 1][temp.y] != maze[temp.x + 1][temp.y])
            {
                maze[temp.x][temp.y] = PATH;
                // 对新加入的通路进行标记
                if (maze[temp.x - 1][temp.y] == FLAG) { maze[temp.x + 1][temp.y] = FLAG; ++temp.x; }
                else { maze[temp.x - 1][temp.y] = FLAG; --temp.x; }
                flag = true;
            }
        }
        // 如果打通了墙,将进加入的通路周围的墙入容器
        if (flag)
        {
            if (temp.x - 1 >= 2 && maze[temp.x - 1][temp.y] == WALL) vp.push_back(Point2(temp.x - 1, temp.y));
            if (temp.x + 1 <= mazeSize - 3 && maze[temp.x + 1][temp.y] == WALL) vp.push_back(Point2(temp.x + 1, temp.y));
            if (temp.y - 1 >= 2 && maze[temp.x][temp.y - 1] == WALL) vp.push_back(Point2(temp.x, temp.y - 1));
            if (temp.y + 1 <= mazeSize - 3 && maze[temp.x][temp.y + 1] == WALL) vp.push_back(Point2(temp.x, temp.y + 1));
        }
        // 移除该墙
        vp[pos] = *(vp.end() - 1);
        vp.pop_back();
    }
    // 将被标记的通路还原
    for (auto& v1 : maze)
        for (auto& v2 : v1)
            if (v2 == FLAG) v2 = PATH;
}

// DFS 寻路
std::list<Point2> DFS_find(const Point2& start, const Point2& end, std::vector<std::vector<int>>& maze)
{
    // 定义方向容器(右、下、左、上)
    std::vector<std::vector<int>> dir{ {1,0},{0,1},{-1,0},{0,-1} };
    // 定义栈容器
    std::list<Point2> sp;
    Point2 temp = start;
    sp.push_back(temp);
    // 与生成算法相似,迭代 DFS 遍历
    while (!sp.empty())
    {
        int i = 0;
        // 若可向其他方向前进,则继续
        for (; i < 4; ++i)
        {
            if (temp.x + dir[i][0] >= 0 && temp.x + dir[i][0] <= mazeSize - 1 && temp.y + dir[i][1] >= 0 && temp.y + dir[i][1] <= mazeSize - 1
                && maze[temp.x + dir[i][0]][temp.y + dir[i][1]] == PATH)
            {
                // 对走过的路进行标记
                maze[temp.x][temp.y] = FLAG;
                temp.x += dir[i][0];
                temp.y += dir[i][1];
                sp.push_back(temp);
                if (temp == end) return sp;
                break;
            }
        }
        // 无路可走时回溯
        if (i == 4)
        {
            maze[temp.x][temp.y] = FLAG;
            sp.pop_back();
            if (!sp.empty()) temp = sp.back();
        }
    }
    // 将被标记的通路还原
    for (auto& v1 : maze)
        for (auto& v2 : v1)
            if (v2 == FLAG) v2 = PATH;

    return sp;
}


// main 函数
int main()
{
    srand((unsigned)time(nullptr));

    // 入口出口
    Point2 start(0, 1);
    Point2 end(mazeSize - 1, mazeSize - 2);

    // 二维迷宫容器
    std::vector<std::vector<int>> maze;

    // 初始化迷宫
    for (int i = 0; i < mazeSize; ++i) maze.push_back(std::vector<int>());
    for (int i = 0; i < mazeSize; ++i)
        for (int j = 0; j < mazeSize; ++j)
            maze[i].push_back(WALL);
    maze[start.x][start.y] = maze[end.x][end.y] = PATH;

    // 生成迷宫
    Prim_generator(maze);

    // 打印迷宫
    for (int j = 0; j < mazeSize; ++j)
    {
        for (int i = 0; i < mazeSize; ++i)
            cout << maze[i][j] << " ";
        cout << endl;
    }

    // 寻路
    auto sp = DFS_find(start, end, maze);
    // 打印路径
    cout << endl << "寻路路径:" << endl;
    int i = 0;
    for (auto p2 : sp)
    {
        if (i++ == 5)
        {
            i = 0;
            cout << "(" << p2.x << ", " << p2.y << ")" << endl;
            continue;
        }
        cout << "(" << p2.x << ", " << p2.y << ")" << "、";
    }
    cout << endl;

    // EasyX
    {
        auto ret = _getwch();
        const int width = 15;
        initgraph(mazeSize * width, mazeSize * width);
        setlinecolor(DARKGRAY);
        setfillcolor(LIGHTGRAY);
        for (int j = 0; j < mazeSize; ++j)
            for (int i = 0; i < mazeSize; ++i)
                if (maze[i][j] == WALL)
                    fillrectangle(i * width, j * width, i * width + width - 1, j * width + width - 1);
        setfillcolor(YELLOW);
        for (auto p2 : sp) fillrectangle(p2.x * width, p2.y * width, p2.x * width + width - 1, p2.y * width + width - 1);
        // saveimage(_T("D:\\maze.png"));
        ret = _getwch();
        closegraph();
    }

    return 0;
}


A* 寻路,一种非递归方法

讲解 A* 寻路算法的文章很多,这里推荐:https://mp.weixin.qq.com/s/FYKR_1yBKR4GJTn0fFIuAA

一些说明:

  1. 用 F 值评估最短路径,公式 F = G + H
  2. G 代表从起点走到当前点的步数,在代码中等于 父节点 的 G 值加 1
  3. H 值是当前点到终点距离的评估,可简单认为是无阻碍时到终点所需的步数 |dx| + |dy|
  4. openList 和 closeList 应该如何选用容器?
  • closeList:只需要将节点加入而不需要其他操作,因此可以使用 vector,list 等容器
  • openList:由于需要从该容器中取出 F 值最小的节点,因此可以考虑使用能够自排序的容器,如 priority_queue,multiset 等
  • 本文使用 list 及 multiset 来实现,且从终点向起点寻路,最终返回一个包含寻路路径的 list 容器

生成形态:

迷宫算法java 迷宫算法论文_#include_20

源码(注释 EasyX 的地方是用 EasyX 绘图):

#include <iostream>
#include <ctime>
#include <set>
#include <list>
#include <stack>
#include <vector>
#include <algorithm>
#include <easyx.h>
using namespace std;


// 迷宫格子状态
enum CellState:int { PATH = 0, WALL, FLAG };

// 迷宫格二维点结构
struct Point2
{
    int x, y;
    Point2(int _x, int _y) :x(_x), y(_y) {}
    bool operator == (const Point2& p2) const { return x == p2.x && y == p2.y; }
};

// 迷宫大小(要求为奇数)
const int mazeSize = 21;


// 迷宫生成接口
void Prim_generator(std::vector<std::vector<int>>& maze)
{
    // 构建墙隔开通路的迷宫,奇数点为通路
    for (int i = 1; i <= mazeSize - 2; i += 2)
        for (int j = 1; j <= mazeSize - 2; j += 2)
            maze[i][j] = PATH;
    // 维护一个墙容器
    std::vector<Point2> vp;
    // 先随机找一个通路
    Point2 temp((rand() % (mazeSize - 2) + 1) | 1, (rand() % (mazeSize - 2) + 1) | 1);
    // 将周围墙入栈
    if (temp.x - 1 >= 2) vp.push_back(Point2(temp.x - 1, temp.y));
    if (temp.x + 1 <= mazeSize - 3) vp.push_back(Point2(temp.x + 1, temp.y));
    if (temp.y - 1 >= 2) vp.push_back(Point2(temp.x, temp.y - 1));
    if (temp.y + 1 <= mazeSize - 3) vp.push_back(Point2(temp.x, temp.y + 1));
    // 标记该通路
    maze[temp.x][temp.y] = FLAG;
    int pos = 0;
    // 后续迭代生成迷宫
    while (!vp.empty())
    {
        // 在墙容器中随机选取一堵墙
        pos = rand() % vp.size();
        temp = vp[pos];
        // 记录该墙是否打通
        bool flag = false;
        // 后续 if else 判断墙所隔离通路在左右还是上下,并判断是否打通
        if (maze[temp.x + 1][temp.y] == WALL)
        {
            if (maze[temp.x][temp.y - 1] != maze[temp.x][temp.y + 1])
            {
                maze[temp.x][temp.y] = PATH;
                // 对新加入的通路进行标记
                if (maze[temp.x][temp.y - 1] == FLAG) { maze[temp.x][temp.y + 1] = FLAG; ++temp.y; }
                else { maze[temp.x][temp.y - 1] = FLAG; --temp.y; }
                flag = true;
            }
        }
        else
        {
            if (maze[temp.x - 1][temp.y] != maze[temp.x + 1][temp.y])
            {
                maze[temp.x][temp.y] = PATH;
                // 对新加入的通路进行标记
                if (maze[temp.x - 1][temp.y] == FLAG) { maze[temp.x + 1][temp.y] = FLAG; ++temp.x; }
                else { maze[temp.x - 1][temp.y] = FLAG; --temp.x; }
                flag = true;
            }
        }
        // 如果打通了墙,将进加入的通路周围的墙入容器
        if (flag)
        {
            if (temp.x - 1 >= 2 && maze[temp.x - 1][temp.y] == WALL) vp.push_back(Point2(temp.x - 1, temp.y));
            if (temp.x + 1 <= mazeSize - 3 && maze[temp.x + 1][temp.y] == WALL) vp.push_back(Point2(temp.x + 1, temp.y));
            if (temp.y - 1 >= 2 && maze[temp.x][temp.y - 1] == WALL) vp.push_back(Point2(temp.x, temp.y - 1));
            if (temp.y + 1 <= mazeSize - 3 && maze[temp.x][temp.y + 1] == WALL) vp.push_back(Point2(temp.x, temp.y + 1));
        }
        // 移除该墙
        vp[pos] = *(vp.end() - 1);
        vp.pop_back();
    }
    // 将被标记的通路还原
    for (auto& v1 : maze)
        for (auto& v2 : v1)
            if (v2 == FLAG) v2 = PATH;
}

// A*寻路容器
class A_Container
{
    struct Node
    {
        Point2 p2;
        int gVal, hVal, fVal;
        Node* parent;
        Node(const Point2& _p2, int _g, int _h, Node* _p) :p2(_p2), gVal(_g), hVal(_h), fVal(_g + _h), parent(_p) {}
    };

public:
    A_Container(const Point2& _start) :destPos(_start) {}
    ~A_Container()
    {
        for (auto& no : openList) delete no;
        for (auto& no : closeList) delete no;
    }

    // 将节点加入 openList
    void pushOpenList(const Point2& _p2)
    {
        int gVal = 0;
        Node* par = nullptr;
        if (!closeList.empty()) { par = *(--closeList.end()); gVal = par->gVal + 1; }
        Node* temp = new Node(_p2, gVal, abs(_p2.x - destPos.x) + abs(_p2.y - destPos.y), par);
        if (_p2.x == destPos.x && _p2.y == destPos.y) p_destNode = temp;
        openList.insert(temp);
        temp = nullptr;
    }

    // 从 openList 中取出 fVal 值最小的节点加入 closeList
    auto getMinNode()
    {
        auto it = openList.begin();
        Point2 ret = (*it)->p2;
        closeList.push_back(*it);
        openList.erase(it);
        return ret;
    }

    // 获取寻路终点,用于回溯得到最短路径
    Node* getDestNode() { return p_destNode; }

    // 用于 multiset 比较 的函数对象
    struct Compare
    {
        bool operator ()(const Node* _n1, const Node* _n2) const { return _n1->fVal < _n2->fVal; }
    };

private:
    Point2 destPos;                                // 目标位置
    std::list<Node*> closeList;                    // closeList 容器
    std::multiset<Node*, Compare> openList;        // openList 自动排序
    Node* p_destNode = nullptr;                    // 用于返回终点的位置指针
};

// A* 寻路
list<Point2> A_find(const Point2& start, const Point2& end, std::vector<std::vector<int>>& maze)
{
    // 从目标位置开始寻找起点,之后回溯即可得到起点到终点的路径
    Point2 temp = end;
    // 创建高效容器
    A_Container cont(start);
    // 将目标位置加入 openList
    cont.pushOpenList(temp);
    // 后续迭代进行,根据 A* 寻路规则,利用高效容器寻找最短路径
    while (true)
    {
        // 将 openList 中 F 值最小的节点放到 closeList 中
        temp = cont.getMinNode();
        // 标记节点加入到 openList 中的节点
        maze[temp.x][temp.y] = FLAG;
        // 后续 if 将周围通路加入 openList
        if (temp.x - 1 >= 0 && maze[temp.x - 1][temp.y] == PATH)
        {
            maze[temp.x - 1][temp.y] = FLAG;
            cont.pushOpenList(Point2(temp.x - 1, temp.y));
            if (start == Point2(temp.x - 1, temp.y)) break;
        }
        if (temp.y - 1 >= 0 && maze[temp.x][temp.y - 1] == PATH)
        {
            maze[temp.x][temp.y - 1] = FLAG;
            cont.pushOpenList(Point2(temp.x, temp.y - 1));
            if (start == Point2(temp.x, temp.y - 1)) break;
        }
        if (temp.x + 1 <= mazeSize - 1 && maze[temp.x + 1][temp.y] == PATH)
        {
            maze[temp.x + 1][temp.y] = FLAG;
            cont.pushOpenList(Point2(temp.x + 1, temp.y));
            if (start == Point2(temp.x + 1, temp.y)) break;
        }
        if (temp.y + 1 <= mazeSize - 1 && maze[temp.x][temp.y + 1] == PATH)
        {
            maze[temp.x][temp.y + 1] = FLAG;
            cont.pushOpenList(Point2(temp.x, temp.y + 1));
            if (start == Point2(temp.x, temp.y + 1)) break;
        }
    }
    // 将被标记的通路还原
    for (auto& v1 : maze)
        for (auto& v2 : v1)
            if (v2 == FLAG) v2 = PATH;
    // 找到起点后,获取高效容器中的起点,回溯得到最短路径
    auto st = cont.getDestNode();
    list<Point2> retList;
    while (st != nullptr)
    {
        retList.push_back({ st->p2 });
        st = st->parent;
    }
    return retList;
}


// main 函数
int main()
{
    srand((unsigned)time(nullptr));

    // 入口出口
    Point2 start(0, 1);
    Point2 end(mazeSize - 1, mazeSize - 2);

    // 二维迷宫容器
    std::vector<std::vector<int>> maze;

    // 初始化迷宫
    for (int i = 0; i < mazeSize; ++i) maze.push_back(std::vector<int>());
    for (int i = 0; i < mazeSize; ++i)
        for (int j = 0; j < mazeSize; ++j)
            maze[i].push_back(WALL);
    maze[start.x][start.y] = maze[end.x][end.y] = PATH;

    // 生成迷宫
    Prim_generator(maze);

    // 打印迷宫
    for (int j = 0; j < mazeSize; ++j)
    {
        for (int i = 0; i < mazeSize; ++i)
            cout << maze[i][j] << " ";
        cout << endl;
    }

    // 寻路
    auto sp = A_find(end, start, maze);
    // 打印路径
    cout << endl << "寻路路径:" << endl;
    int i = 0;
    for (auto p2 : sp)
    {
        if (i++ == 5)
        {
            i = 0;
            cout << "(" << p2.x << ", " << p2.y << ")" << endl;
            continue;
        }
        cout << "(" << p2.x << ", " << p2.y << ")" << "、";
    }
    cout << endl;

    // EasyX
    {
        auto ret = _getwch();
        const int width = 15;
        initgraph(mazeSize * width, mazeSize * width);
        setlinecolor(DARKGRAY);
        setfillcolor(LIGHTGRAY);
        for (int j = 0; j < mazeSize; ++j)
            for (int i = 0; i < mazeSize; ++i)
                if (maze[i][j] == WALL)
                    fillrectangle(i * width, j * width, i * width + width - 1, j * width + width - 1);
        setfillcolor(YELLOW);
        for (auto p2 : sp) fillrectangle(p2.x * width, p2.y * width, p2.x * width + width - 1, p2.y * width + width - 1);
        // saveimage(_T("D:\\maze.png"));
        ret = _getwch();
        closegraph();
    }

    return 0;
}