zoukankan      html  css  js  c++  java
  • 栈和队列应用:迷宫问题

    迷宫寻路

    应用情景

    例如如图所示迷宫,黄色方格代表起点,橙色方格代表终点,绿色方格代表可走路径,蓝色方格代表障碍物。已知这是一个 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语言版|第二版)》—— 严蔚敏 李冬梅 吴伟民 编著,人民邮电出版社

  • 相关阅读:
    jQuery标签操作
    Bootstrap和Font Awesome
    jQuery拾遗
    Bootstrap笔记
    软件测试
    Day01 第一个Python程序
    cd指令
    ls命令
    type命令
    每天一个Linux指令
  • 原文地址:https://www.cnblogs.com/linfangnan/p/14611321.html
Copyright © 2011-2022 走看看