栈和队列应用:迷宫问题

迷宫寻路

应用情景

例如如图所示迷宫,黄色方格代表起点,橙色方格代表终点,绿色方格代表可走路径,蓝色方格代表障碍物。已知这是一个 M × N 大小的迷宫,可以用 0 表示可走路径,1 表示障碍,算法要求实现从迷宫的任意一点出发,试探出一条通向终点的路径。

应用解析

刚看到这个情景,我们是一头雾水的,因此在开始解析之前,我们先把迷宫的构成说明白。如图是一个我已经鸽了很久的 RPG 游戏制作页面,我们发现这个页面和游戏的界面是不一样的,游戏的舞台被一个个方格所切割,制作这类的游戏时,我无论是绘制地图、设置事件还是踩雷遇怪,都是通过对这些方格填充内容实现的,而这些方格在一张确定大小的地图上都是有对应坐标的。

我们是怎么定位可控角色在地图的位置的?其实也是通过这些坐标,确定好角色所在的方格之后就把角色的贴图填充进去。当角色进行移动时,我们先获取这个角色的坐标,确定移动到哪个方格之后,播放设置好的行走图即可实现。不知道爱玩游戏的你是否想过这些问题呢?(笑)

深度优先

现在我们已经把背景说明白了,再来思考一下,我们可以把需求的迷宫描述为一个二维数组,二维数组抽象成几何图形的时候是一个矩阵,因此我们的问题就变成了描述起点到终点坐标的问题了。这个时候我们就要模拟一个玩家,这个玩家要标记他走过的坐标,由于要自动寻路,有东南西北四个维度,因此我们就以这个顺序先作为玩家的寻路顺序。当玩家遇到死路时,就要退回之前走过的路,因此我们就发现了栈结构“后进先出”的特性很适合用于描述走回头路的过程。

代码实现

#include<iostream>
#include<stack>
using namespace std;
#define M 8
#define N 8
typedef struct
{
    int x;    //路径的 x 坐标
    int y;    //路径的 y 坐标
    int next_direction = 1;    //表示方位,由 1 到 4 分别表示东南西北
} unit;
void Labyrinth(int xi, int yi, int xe, int ye);    //走迷宫函数
void exploreWay(int x, int y, stack<unit>& path, unit& a_unit);    //探路函数

int a_maze[M + 2][N + 2] =
{
    {1,1,1,1,1,1,1,1,1,1},
    {1,0,0,1,0,0,0,1,0,1},
    {1,0,0,1,0,0,0,1,0,1},
    {1,0,0,0,0,1,1,0,0,1},
    {1,0,1,1,1,0,0,0,0,1},
    {1,0,0,0,1,0,0,0,0,1},
    {1,0,1,0,0,0,1,0,0,1},
    {1,0,1,1,1,0,1,1,0,1},
    {1,1,0,0,0,0,0,0,0,1},
    {1,1,1,1,1,1,1,1,1,1}
};

int main() 
{
    Labyrinth(1, 1, M, N);
    for (int i = 0; i < M + 2; i++)    //打印迷宫路径
    {
        for (int j = 0; j < N + 2; j++)
        {
            if (a_maze[i][j] == 4)
                cout <<  "  ";
            else
                cout << a_maze[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

void exploreWay(int x, int y, stack<unit>& path, unit& a_unit)
{
    if (a_maze[x][y] == 0)    //该方向路可以走
    {
        a_unit.x = x;
        a_unit.y = y;
        a_maze[x][y] = 2;
        path.push(a_unit);    //新路径入栈
    }
    else    //改变方向,准备下一次探路
        path.top().next_direction++;
}

void Labyrinth(int xi, int yi, int xe, int ye)    //探查下一个可走的路径
{
    stack<unit> path;
    unit a_unit;

    exploreWay(xi, yi, path, a_unit);
    while (!path.empty())
    {
        if (path.top().x == xe && path.top().y == ye)
            break;
        switch (path.top().next_direction)
        {
        case 1:    //向东探路
            exploreWay(path.top().x + 1, path.top().y, path, a_unit);
            break;
        case 2:    //向南探路
            exploreWay(path.top().x, path.top().y + 1, path, a_unit);
            break;
        case 3:    //向西探路
            exploreWay(path.top().x - 1, path.top().y, path, a_unit);
            break;
        case 4:    //向北探路
            exploreWay(path.top().x, path.top().y - 1, path, a_unit);
            break;
        default:    //走到死路
            a_maze[path.top().x][path.top().y] = 9;    //标记为死路
            path.pop();    //栈顶退栈
        }
    }
    while (!path.empty())    //为了打印路径,给迷宫挖空
    {
        a_maze[path.top().x][path.top().y] = 4;
        path.pop();
    }
}

运行效果

  • 虽然这段代码是以深度优先搜索为基础写出来的,但是这并不完整,因为理论上深度优先搜索是可以找到所有路径的。解决方案是找到终点之后仍然走回头路,在有岔路的地方再次进行试探,直到没有岔路可走为止。此处只是为了展示栈结构的应用,深度优先搜索并不是我们当前要谈的问题,因此我简化的操作,并且将路径挖空,帮助我们更好地理解。

迷宫寻路(广度优先)

应用解析

在这里我们想利用广度优先的思想来实现,本算法的思想是从 (xi,yi) 开始,利用队列的特点,一层一层扩大搜索的直径,把可走的点都导入到队列中,直到搜索到终点。

不过我这里主要是为了展示队列的应用,因此对于广度优先我在这里不过多阐述,可以自行查阅相关资料理解。这里需要强调的是,由于我们需要得到完整的路径,也就是说搜索过的路径不能够真出队列,以便于我们得到答案,因此就不能使用 STL 库的 queue 容器来实现,自建队列的话就要使用顺序队列来描述。不过我认为此处最适合的是 STL 库的 vector 容器,我们只需要定义两个游标来描述 vector 对象的队列头和尾的位置,就可以使用 vector 的方法来实现插入等操作,我们在解决银行排队问题的时候也是这么做的。

代码实现

#include<iostream>
#include<vector>
using namespace std;
#define M 8
#define N 8
typedef struct
{
    int x;
    int y;    //路径的坐标
    int pre;    //表示该路径前驱的游标
} unit;
int exploreWay(int x, int y, vector<unit>& path, unit& a_unit, int front, int& rear);    //添加单个路径
void Labyrinth(int xi, int yi, int xe, int ye, vector<unit>& min_path);    //路径搜索函数
int a_maze[M + 2][N + 2] =
{
    {1,1,1,1,1,1,1,1,1,1},
    {1,0,0,1,0,0,0,1,0,1},
    {1,0,0,1,0,0,0,1,0,1},
    {1,0,0,0,0,1,1,0,0,1},
    {1,0,1,1,1,0,0,0,0,1},
    {1,0,0,0,1,0,0,0,0,1},
    {1,0,1,0,0,0,1,0,0,1},
    {1,0,1,1,1,0,1,1,0,1},
    {1,1,0,0,0,0,0,0,0,1},
    {1,1,1,1,1,1,1,1,1,1}
};

int main()
{
    vector<unit> min_path;    //存储最短路径
    Labyrinth(1,1, M, N,min_path);
    cout << "最短路径为:" << endl;
    for (int i = min_path.size() - 1; i >= 0; i--)
    {
        cout << "(" << min_path[i].x << " , " << min_path[i].y << ")   ";
        if ((min_path.size() - i) % 5 == 0)
            cout << endl;
    }
	return 0;
}

int exploreWay(int x, int y, vector<unit>& path, unit& a_unit, int front, int& rear)
{
    if (a_maze[x][y] == 0)    //若路径可走
    {
        a_unit.x = x;
        a_unit.y = y;
        a_unit.pre = front;
        path.push_back(a_unit);    //添加路径
        rear++;    //移动尾指针
        a_maze[x][y] = 2;
        return 1;
    }
    return 0;
}

void Labyrinth(int xi, int yi, int xe, int ye, vector<unit>& min_path)    //搜索路径为:(xi,yi)->(xe,ye)
{
    vector<unit> path;    //存储所有可走路径
    unit a_unit;
    int front, rear = -1;

    front = rear;
    exploreWay(xi, yi, path, a_unit, front, rear);    //添加起点路径,同时初始化头尾指针
    while (rear != front)
    {
        front++;                                                 //判断路径东侧结点是否添加
        if (exploreWay(path[front].x + 1, path[front].y, path, a_unit, front, rear) == 1 
                                                                && a_unit.x == xe && a_unit.y == ye)
            break;                                               //判断路径南侧结点是否添加
        if (exploreWay(path[front].x, path[front].y + 1, path, a_unit, front, rear) == 1
                                                                && a_unit.x == xe && a_unit.y == ye)
            break;                                               //判断路径西侧结点是否添加
        if (exploreWay(path[front].x - 1, path[front].y, path, a_unit, front, rear) == 1
                                                                 && a_unit.x == xe && a_unit.y == ye)
            break;                                               //判断路径北侧结点是否添加
        if (exploreWay(path[front].x, path[front].y - 1, path, a_unit, front, rear) == 1
                                                                 && a_unit.x == xe && a_unit.y == ye)
            break;
    }
    while (rear != -1)    //将搜索到的最短路径移动到 min_path
    {
        min_path.push_back(path[rear]);
        rear = path[rear].pre;
    }
}

运行效果

参考资料

《大话数据结构》—— 程杰 著,清华大学出版社
《数据结构教程》—— 李春葆 主编,清华大学出版社
《数据结构(C语言版|第二版)》—— 严蔚敏 李冬梅 吴伟民 编著,人民邮电出版社

posted @ 2021-04-03 00:05  乌漆WhiteMoon  阅读(431)  评论(0编辑  收藏  举报