迷宮算法


迷宮算法之迷宮生成和迷宮尋路算法



三種迷宮生成算法

  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. x,y 均在 1~N-2 中的奇數隨機選取一點(均為奇數),將其挖開,並將該點入棧

  3. 四個方向隨機選取一個方向,設當前挖開坐標為(x, y),若該方向上滿足 (x + dx*2, y + dy*2) 是牆(dx 和 dy 代表方向,取值為 1 或 -1),則挖開 (x + dx, y + dy),並重設當前點為 (x + dx*2, y + dy*2),將當前點入棧

  4. 以 Cur 為當前點,重復操作步驟 2

  5. 若 Cur 不能挖開周圍的牆,則棧執行 pop 操作,並將 Cur 重置為此時棧中的 top 元素

  6. 直到棧為空,說明挖牆操作結束

生成形態:

源碼(包含迭代和非迭代版,注釋 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. x,y 均在 1~N-2 中隨機選取一點(均為偶數),然后十字建牆

  3. 在建好的四面牆(不包含選取點)中隨機選擇三面,找奇數點開洞,使得四個子空間連通

  4. 對四個子空間重復操作,直到子空間不可分割為止(淡黃色是開洞位置)(何時不可分割?長度 或 寬度 不大於 1 時)

生成形態:

源碼(包含迭代和非迭代版,注釋 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. 構建一牆一通路形式

  3. 隨機選擇一個通路,並將周圍牆入容器,標記該通路

  4. 在牆容器中隨機選取一堵牆,如果牆兩邊的通路沒有同時被標記,則打通該牆,並將原來未被標記的通路周圍的牆加入容器,然后將該通路標記,最后移除該牆

  5. 重復操作 4,直到牆容器為空,說明該算法完成,最后將被標記的通路清除標記

生成形態:

源碼(注釋 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 尋路,采用非遞歸實現

該尋路方法,應該算是比較簡單算法,學習數據結構時一般就會接觸該算法

從起點開始,將當前點入棧,向某一方向前進。若周圍方向均不能繼續前進,則回退,直到找到終點或棧為空,就不再貼圖

生成形態:

源碼(注釋 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 容器

生成形態:

源碼(注釋 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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM