[week2]经典迷宫问题——BFS搜索法

题意

东东有一张地图,想通过地图找到妹纸。地图显示,0表示可以走,1表示不可以走,左上角是入口,右下角是妹纸,这两个位置保证为0。既然已经知道了地图,那么东东找到妹纸就不难了,请你编一个程序,写出东东找到妹纸的最短路线。

Input

输入是一个5 × 5的二维数组,仅由0、1两数字组成,表示法阵地图。

Output

输出若干行,表示从左上角到右下角的最短路径依次经过的坐标,格式如样例所示。数据保证有唯一解。

输入样例

0 1 0 0 0
0 1 0 1 0
0 1 0 1 0
0 0 0 1 0
0 1 0 1 0

输出样例

(0, 0)
(1, 0)
(2, 0)
(3, 0)
(3, 1)
(3, 2)
(2, 2)
(1, 2)
(0, 2)
(0, 3)
(0, 4)
(1, 4)
(2, 4)
(3, 4)
(4, 4)

分析

这是一个需要利用BFS搜索算法解决的经典迷宫问题

  • 首先考虑,在计算机中应该用什么方式表示迷宫?

最典型的方法就是用01组成的二维数组来模拟迷宫。二维数组的下标即为其对应表示的点在迷宫中的坐标,每个数组储存的元素即为在迷宫中相同坐标处一点的通行状况(0为可通行,1为不可通行)。
本代码中,利用STL中的map实现一个迷宫节点类型到布尔值的映射,以此代替二维数组。【为什么这么写?单纯是因为反正都要用这个节点类型(狗头)】
迷宫节点类型——包含x和y两个坐标值。并且重载<和=比较符号,为了实现之后同类型的比较操作。

  • 啥是BFS?为什么用BFS?

BFS(宽度优先搜索)是图的一种搜索算法。可以理解为持续遍历与已搜索节点相连的各点,直到找到目标点或是将所有直接或间接相连的节点遍历完全。(听上去和DFS很像)

举个🌰——假设当前你正在A点处,与A相连的有n个点A1~An,这n个点都将成为你下一次前进的备选,你的下一步可以从这所有备选中选择一个。当你选择了一个备选为下一步时,就将该备选去掉。到达你选择的下一个点后,发现与该点相连的有m个点,则这m个点也将进入你的备选中,此时你的备选就有n+m-1个。重复这样的操作,直到在过程中你找到想要的点,或者没有备选可选了。

【小tip:备选的选择是随机的,也就是说一般都是没有规律的。但是可以通过改变备选的存储和提取方式使得选择不那么随机。比如把备选按升序存放,每次取第一个。】

因此在迷宫问题中,你可以看成一个小人从起点开始不断尝试备选的下一步,而只有能通行的下一步才可以成为备选,直到最后他到达终点。

  • BFS算法实现(路径的查找)

BFS需要利用队列实现,也就是前面所说的备选需要用队列(先进先出)这种结构来存放。
为了防止重复访问那些已经搜索过的(可以理解为已经成为过备选并已经被去掉过的)点,因此需要用一个数组(本代码中的visit数组)来记录每一个点是否已被访问。若一个点的邻接点已被访问,就不考虑将其再次放入队列(备选)中。

BFS过程

首先将起点压入队列中。取出并删除队列中的首元素,对该点的邻接点依次遍历,若未被访问且可通行,将其压入队列中,并标记该点为已访问,将其记录到路径数组中。邻接点遍历方法为循环四次计算x和y的偏移量,得到该点坐标上下左右的四个新坐标,并判断这四个坐标是否合法(是否在迷宫坐标范围内),若合法则进行前述操作。若计算得到终点,结束搜索。队列不为空时,重复上述操作。【计算偏移量就不讲了吧(狗头*2)】

【小tip:判断下标合法的方式不止这一种。还可以在迷宫数组的外围加上一圈1,表示边界。这样就可以省略根据坐标范围判断下标合法性的操作。】

  • 路径的记录和输出

利用数组记录,每个坐标映射到达该坐标的上一个坐标(并查集)。本代码中利用STL的map实现了一个迷宫节点到迷宫节点的映射。
通过递归完成路径的输出。从终点回溯到起点,再依次输出。【递归输出路径就懒得讲了(狗头*3)】

总结

  1. 自定义结构体如果需要用到==的比较符,也需要在结构体重载。
  2. 递归输出路径的方法请不要再忘记👋
  3. map真好用🤦‍♂️

代码

//
//  main.cpp
//  lab-a
//
#include <iostream>
#include <map>
#include <queue>
using namespace std;

struct POINT                //迷宫节点类型
{
    int x,y;        //坐标
    
    POINT(){}       //构造函数
    POINT(int x1,int y1):x(x1),y(y1){}
    
    bool operator < (const POINT &p) const      //重载<
    {
        return x != p.x ? x < p.x : y < p.y;
    }
    bool operator == (const POINT &p) const      //重载=
    {
        if( x == p.x && y == p.y )
            return true;
        else
            return false;
    }
};

POINT start(0,0),destination(4,4),ending(-1,-1);           //起点和终点,以及判定
map<POINT,bool> maze;       //迷宫数组,每个点映射其是否能走,0为能走
map<POINT, POINT> path;     //路径数组,每个点映射到达该点的上一个点
map<POINT,bool> visit;      //记录每个点是否已访问,0为未访问
queue<POINT> q;             //队列,用于bfs
int xx[]={0,0,1,-1};        //x坐标的四个位移量
int yy[]={1,-1,0,0};        //y坐标的四个位移量

void bfs()                  //宽度搜索查找路径
{
//    cout<<" && "<<endl;
    q.push(start);     //将起点压入队列中
    
    visit[start]=1;          //将起点标为已到达
    path[POINT(0, 0)]=POINT(-1,-1);
    
    while( !q.empty() ) //当队列不为空时继续判断
    {
//        cout<<"^^"<<endl;
        POINT m=q.front();          //取出队列首元素并将其弹出
        q.pop();
        
//        if( visit[POINT(m.x, m.y)] )       //若当前取出点已被访问则直接进入下一次循环啊
//            continue;
        
        for( int i = 0 ; i < 4 ; i++ )  //判断当前点的四个位移点
        {
//            cout<<i<<" ## "<<endl;
            //依次计算得到四个相邻点
            int x1=m.x+xx[i];
            int y1=m.y+yy[i];
            
            //判断当前相邻点坐标是否合法、是否可走、是否已被访问
            if( x1 >= 0 && x1 < 5 && y1 >= 0 && y1 < 5 && !visit[POINT(x1, y1)] && !maze[POINT(x1,y1)])
            {
                visit[POINT(x1, y1)]=1;            //若都满足要求,将该点标记为已被访问
                q.push(POINT(x1,y1));               //将该点压入队列
                path[POINT(x1, y1)]=POINT(m.x,m.y);     //记录到达该点的上一个点
                
                if( x1 == 4 && y1 == 4 )        //若当前访问点为终点即结束bfs
                    return;
            }
        }
    }
    
}

void output(POINT p)                //输出路径
{
    if( path[p] == ending )             //如果当前输出点是起点,则开始从此输出
         cout<<"("<<p.x<<", "<<p.y<<")"<<endl;
    else
    {
        output(path[p]);                        //查找当前坐标的上一个坐标
        cout<<"("<<p.x<<", "<<p.y<<")"<<endl;
    }
}

int main()
{
    ios::sync_with_stdio(false);	//关闭同步
    
    for( int i = 0 ; i < 5 ; i++ )		//输入并初始化迷宫
        for( int j = 0 ; j < 5 ; j++ )
        {
            cin>>maze[POINT(i,j)];
            visit[POINT(i, j)] =0;
        }

    bfs();		//查找路径
//    POINT a=start;
    output(destination);		//输出路径

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天翊藉君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值