关闭

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

标签: 数据结构电路布线问题最短路径求解算法原理队列的应用广度优先搜索算法
1909人阅读 评论(0) 收藏 举报
分类:

迷宫最短路径问题(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)前进方向示意图:
    这里写图片描述
  • (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 <iostream>
    
    
    using namespace std;
    
    template <class T>
    struct LinkNode         //链表结点类的定义
    {
        T data;             //数据域
        LinkNode<T> *link;  //指针域——后继指针
        //仅初始化指针成员的构造函数
        LinkNode(LinkNode<T>* ptr = NULL){ link = ptr; }
        //初始化数据与指针成员的构造函数
        LinkNode(const T& value, LinkNode<T>* ptr = NULL){ data = value; link = ptr; }
    };
    
    template <class T>
    class LinkedQueue
    {
    public:
        LinkedQueue();                      //构造函数
        ~LinkedQueue();                     //析构函数
    public:
        LinkNode<T>* getHead() const;   //获取队头结点
        bool EnQueue(const T& x);       //新元素x入队
        bool DeQueue(T& x);             //队头元素出队,并将该元素的值保存至x
        bool IsEmpty() const;           //判断队列是否为空
        void MakeEmpty();               //清空队列的内容
    private:
        LinkNode<T> *front; //队头指针,即链头指针
        LinkNode<T> *rear;  //队尾指针,即链尾指针
    };
    
    //构造函数
    template <class T>
    LinkedQueue<T>::LinkedQueue()
    : front(NULL), rear(NULL)
    {
        cout << "$ 执行构造函数" << endl;
    }                       
    
    //析构函数
    template <class T>
    LinkedQueue<T>::~LinkedQueue()
    {
        cout << "$ 执行析构函数" << endl;
        MakeEmpty();
    }   
    
    //获取队头结点
    template <class T>
    LinkNode<T>* LinkedQueue<T>::getHead() const
    {
        return front;
    }
    
    //新元素x入队
    template <class T>
    bool LinkedQueue<T>::EnQueue(const T& x)
    {
        LinkNode<T> *newNode = new LinkNode<T>(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<T>::DeQueue(T& x)
    {
        if (true == IsEmpty())
        {
            return false;
        }
        LinkNode<T> *curNode = front;
        front = front->link;
        x = curNode->data;
        delete curNode;
        return true;
    }
    
    //判断队列是否为空
    template <class T>
    bool LinkedQueue<T>::IsEmpty() const
    {
        return (NULL == front) ? true : false;
    }
    
    //清空队列的内容
    template <class T>
    void LinkedQueue<T>::MakeEmpty()
    {
        LinkNode<T> *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 <iostream>
    
    
    #include <windows.h>
    
    
    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<items>* linkedQueue = new LinkedQueue<items>;
        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 迷宫问题求解结果

  • 控制台输出,迷宫通路是绿色高亮显示的路径。
    这里写图片描述

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

2
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:206564次
    • 积分:5548
    • 等级:
    • 排名:第5272名
    • 原创:158篇
    • 转载:0篇
    • 译文:0篇
    • 评论:22条