迷宫最短路径问题(ShortestPath)的求解——利用链式队列

迷宫最短路径问题(ShortestPath)的求解——利用链式队列

注:借助于栈求解迷宫问题时,并不能保证找到一条从迷宫入口到迷宫出口的最短路径。而借助于队列,可以找到从迷宫入口到迷宫出口的最短路径(如果有的话)。在迷宫中寻找最短路径问题在其他领域也存在,例如,在解决电路布线问题时,一种很常用的方法是在布线区域叠上一个网格,该网格把布线区域划分成n*m个方格,就像迷宫一样。从一个方格a的中心点连接到另一个方格b的中心点时,转弯处必须采取直角,如果已经有某条线路经过一个方格,则封锁该方格。希望使用a和b之间的最短路径来作为布线的路径,以便减少信号的延迟。

1. 迷宫问题的提法

  • 迷宫问题是典型的图的搜索问题。
  • 假设一个迷宫,只有一个入口和一个出口。如果从迷宫的入口到达出口,途中不出现行进方向错误,则得到一条最佳路线。
  • 为此,用一个二维数组maze[m][n]来表示迷宫。
    (1)当数组元素maze[i][j]=1 (0≤i≤m-1,1≤j≤n-1),表示该位置是墙壁,不能通行。
    (2)当数组元素maze[i][j]=0 (0≤i≤m-1,1≤j≤n-1),表示该位置是通路,可以通行。
  • 注:数组的第0行、第m-1行,第0列、第n-1列,必须是迷宫的围墙,即上述行列的所有坐标对应的数值必须为1(除了入口和出口两个位置的坐标对应的数值可以为0以外),不能通行。

2. 利用队列求解最短路径的算法原理

  • 先从位置a开始搜索,把从a可到达的相邻方格都标记为1(表示与a的距离为1)。
  • 然后把从标记为1的方格可到达的相邻方格都标记为2(表示与a的距离为2)。
  • 如此继续标记下去,直到到达位置b或者找不到可到达的相邻方格为止。
  • 按照上述搜索过程,当最后到达b时,就可以在b上读出b与a之间的距离。
  • 为了得到a和b之间的最短路径,从b开始,首先移动到一个比b的标号小的相邻位置上,一定存在这样的相邻位置,因为任一个方格上的标号与它相邻方格上的编号都相差1。
  • 设任一时刻在迷宫中的位置[i][j]标记为X,X周围有4个前进方向,它实际是一系列交通路口,如果某一方向是0值,表示该方向有路可通,反之表示该方向已堵死。
  • 为了有效地选择下一位置,可以将从位置[i][j]出发可能的前进方向预先定义在一个表内,按顺时针方向为Right([i][j+1]),Down([i+1][j]),Left([i][j-1]),Up([i-1][j])。
  • (1)前进方向示意图:
    迷宫最短路径问题(ShortestPath)的求解——利用链式队列_第1张图片
  • (2)前进方向表:

    Move[q].dir move[q].a move[q].b
    “N” -1 0
    “E” 0 1
    “S” 1 0
    “W” 0 -1

3. 利用队列求解最短路径

3.1 链式队列的类定义及其操作的实现

  • 文件:LinkedQueue.h

    
    #ifndef LINKED_QUEUE_H_
    
    
    #define LINKED_QUEUE_H_
    
    
    
    #include 
    
    
    using namespace std;
    
    template <class T>
    struct LinkNode         //链表结点类的定义
    {
        T data;             //数据域
        LinkNode *link;  //指针域——后继指针
        //仅初始化指针成员的构造函数
        LinkNode(LinkNode* ptr = NULL){ link = ptr; }
        //初始化数据与指针成员的构造函数
        LinkNode(const T& value, LinkNode* ptr = NULL){ data = value; link = ptr; }
    };
    
    template <class T>
    class LinkedQueue
    {
    public:
        LinkedQueue();                      //构造函数
        ~LinkedQueue();                     //析构函数
    public:
        LinkNode* getHead() const;   //获取队头结点
        bool EnQueue(const T& x);       //新元素x入队
        bool DeQueue(T& x);             //队头元素出队,并将该元素的值保存至x
        bool IsEmpty() const;           //判断队列是否为空
        void MakeEmpty();               //清空队列的内容
    private:
        LinkNode *front; //队头指针,即链头指针
        LinkNode *rear;  //队尾指针,即链尾指针
    };
    
    //构造函数
    template <class T>
    LinkedQueue::LinkedQueue()
    : front(NULL), rear(NULL)
    {
        cout << "$ 执行构造函数" << endl;
    }                       
    
    //析构函数
    template <class T>
    LinkedQueue::~LinkedQueue()
    {
        cout << "$ 执行析构函数" << endl;
        MakeEmpty();
    }   
    
    //获取队头结点
    template <class T>
    LinkNode* LinkedQueue::getHead() const
    {
        return front;
    }
    
    //新元素x入队
    template <class T>
    bool LinkedQueue::EnQueue(const T& x)
    {
        LinkNode *newNode = new LinkNode(x);
        if (NULL == newNode)
        {
            return false;
        }
    
        if (NULL == front)
        {
            front = newNode;
            rear = newNode;
        }
        else
        {
            rear->link = newNode;
            rear = rear->link;
        }
        return true;
    }
    
    //队头元素出队,并将该元素的值保存至x
    template <class T>
    bool LinkedQueue::DeQueue(T& x)
    {
        if (true == IsEmpty())
        {
            return false;
        }
        LinkNode *curNode = front;
        front = front->link;
        x = curNode->data;
        delete curNode;
        return true;
    }
    
    //判断队列是否为空
    template <class T>
    bool LinkedQueue::IsEmpty() const
    {
        return (NULL == front) ? true : false;
    }
    
    //清空队列的内容
    template <class T>
    void LinkedQueue::MakeEmpty()
    {
        LinkNode *curNode = NULL;
        while (NULL != front)           //当链表不为空时,删去链表中所有结点
        {
            curNode = front;            //保存被删结点
            front = curNode->link;      //被删结点的下一个结点成为头结点
            delete curNode;             //从链表上摘下被删结点
        }
    }
    
    
    #endif /* LINKED_QUEUE_H_ */
    

3.2 迷宫的初始化及前进方向表的定义

  • 文件:MazeConfig.h

    
    #ifndef MAZECONFIG_H_
    
    
    #define MAZECONFIG_H_
    
    
    
    #include 
    
    
    #include 
    
    
    using namespace std;
    
    //位置坐标和前进方向序号的三元组结构定义
    struct items
    {
        int x;//位置的x坐标
        int y;//位置的y坐标
    };
    
    //前进方向表的结构定义
    struct offfsets
    {
        int a;//x方向的偏移
        int b;//y方向的偏移
        char *dir;//移动的方向描述
    };
    
    const int m = 9;//迷宫的行数
    const int n = 9;//迷宫的列数
    const int dir_count = 4;//前进方向的总数
    const int pathmark = -1;//迷宫通路的标识值
    
    items entry = { 3, 2 };//迷宫入口网格坐标
    items exitus = { 4, 6 };//迷宫出口网格坐标
    
    //各个方向的偏移表定义
    offfsets moves[dir_count] =
    {
        { 0, 1, "Right" },
        { 1, 0, "Down" },
        { 0, -1, "Left" },
        { -1, 0, "Up" }
    };
    
    //初始化迷宫
    int Maze[m][n] =
    {
        { 1, 1, 1, 1, 1, 1, 1, 1, 1 },
        { 1, 0, 0, 1, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 1, 1, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 0, 1, 0, 0, 1 },
        { 1, 0, 0, 0, 1, 1, 0, 0, 1 },
        { 1, 1, 0, 0, 0, 1, 0, 0, 1 },
        { 1, 1, 1, 1, 0, 0, 0, 0, 1 },
        { 1, 1, 1, 1, 0, 0, 0, 0, 1 },
        { 1, 1, 1, 1, 1, 1, 1, 1, 1 }
    };
    
    //打印迷宫
    void print_maze()
    {
        cout << "======>MazePath" << endl;
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {
                if (Maze[i][j] == pathmark)
                {
                    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN);
                }
                else
                {
                    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY);
                }  
                cout.width(4);  //设置字段宽度为n位
                cout << Maze[i][j];
            }
            cout << endl;
        }
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY);
    }
    
    
    #endif /* MAZECONFIG_H_ */
    

3.3 最短路径的求解算法实现

  • 文件:SeekShortestPath.h

    
    #ifndef SEEKPATH_SHORTEST_H_
    
    
    #define SEEKPATH_SHORTEST_H_
    
    
    
    #include "MazeConfig.h"
    
    
    #include "LinkedQueue.h"
    
    
    void MarkPath()
    {
        int PathLen = Maze[exitus.x][exitus.y];
        cout << "迷宫最短路径长度:" << PathLen << endl;
        items *path = new items[PathLen];
        items cur = exitus;
        items tmp, next;
        for (int j = PathLen - 1; j >= 0; j--)
        {
            tmp = path[j] = cur;
            Maze[tmp.x][tmp.y] = pathmark;
            for (int i = 0; i < dir_count; i++)
            {
                next.x = cur.x + moves[i].a;
                next.y = cur.y + moves[i].b;
                if (Maze[next.x][next.y] == j)
                {
                    break;
                }
            }
            cur = next;
        }
    }
    
    bool SeekShortestPath()
    {
        LinkedQueue* linkedQueue = new LinkedQueue;
        items cur = entry;
        items next = entry;
        Maze[entry.x][entry.y] = 1;
        while ((next.x != exitus.x) || (next.y != exitus.y))
        {
            for (int d = 0; d < dir_count; d++)
            {
                next.x = cur.x + moves[d].a;
                next.y = cur.y + moves[d].b;
                if (Maze[next.x][next.y] == 0)
                {
                    Maze[next.x][next.y] = Maze[cur.x][cur.y] + 1;
                    if ((next.x == exitus.x) && (next.y == exitus.y))
                    {
                        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN);
                        cout << "======>SeekShortestPath Success" << endl;
                        break;
                    }
                    linkedQueue->EnQueue(next);
                }
            }
            if (true == linkedQueue->IsEmpty())
            {
                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_INTENSITY | FOREGROUND_RED);
                cout << "======>SeekPath Fail" << endl;
                delete linkedQueue;
                return false;
            }
            linkedQueue->DeQueue(cur);
        }
        MarkPath();
        delete linkedQueue;
        return true;
    }
    
    
    #endif /* SEEKPATH_SHORTEST_H_ */
    

3.4 主函数(main函数)的实现

  • 文件:main.cpp

    
    #include "SeekShortestPath.h"
    
    
    int main(int argc, char* argv[])
    {
        print_maze();
        SeekShortestPath();
        print_maze();
        system("pause");
        return 0;
    }

3.5 迷宫问题求解结果

  • 控制台输出,迷宫通路是绿色高亮显示的路径。
    迷宫最短路径问题(ShortestPath)的求解——利用链式队列_第2张图片

参考文献:
[1]《数据结构(用面向对象方法与C++语言描述)(第2版)》殷人昆——第三章
[2] 百度搜索关键字:迷宫问题、队列、广度优先搜索算法

你可能感兴趣的:(C++数据结构)