bfs—宽(广)度优先搜索(模板详解及各种拓展)

目录

前言

bfs模板的介绍

 实现bfs的重要数据结构——队列

bfs的算法思路 

准备一道模板题

bfs的性质  

数组上的准备 

关于bfs一些细节上的代码 

bfs完整代码实现

 一些习题

 bfs的拓展应用

 从数组准备中延申出的拓展

 从图的本身去做思考

最后的一点总结 


 

 大家看看今天的博客行不刑

前言

在计算机领域中,的是个很特殊的存在。

图可以是具体的:

比如在地图中,图中的点可以是一个个城市,边可以是城市中的一条条道路。

图也可以是抽象的:

点可以代表某种状态,边则是状态之间的转移方式

因此,如何遍历图,如何有效得搜索图成为了一个新的问题。

这就需要引出今天得主角,经典搜索方式之一——bfs

bfs模板的介绍

bfs,dfs是图论的两种主要搜索方式,bfs相比于dfs,有着较为固定的基本模板,所以学起来不会太难。

 实现bfs的重要数据结构——队列

在开始学习bfs之前,我们需要了解一种与bfs息息相关的数据结构——队列

队列的实现非常简单,不管是手写,还是使用stl,都不会太复杂。

什么是队列

 

 为什么要用队列

 在bfs中,我们可能走到的每一个点或者状态,都要保存在队列中。

stl的实现

#include<queue>
queue<int> que;//定义队列
que.push(x);//元素加入队尾
que.front();//获取队首
que.pop();//队首元素出队
que.empty();//队列判空

 手写队列的实现

int que[N],tail = -1,head = 0;//初始化
que[++tail] = x;//元素入队
x = que[head++];//取出队首,并将队首元素出队
tail>=head;//队列判空

推荐尽量手写,因为手写出的队列有着较小的常数,速度更快

 stl:功能强大当然是有代价的

bfs的算法思路 

  1. 把起点放入到队列当中
  2. 从起点开始按照一定的规则走到其他的点,并放入队尾
  3. 取出队列中的队首元素,重复步骤2
  4. 搜索到终点,算法结束 

明白了基本的原理,就可以开始了。

准备一道模板题

题意:

给定一个n*m大小的矩阵

0为空地,1为障碍

求从(1,1)到(n,m)的最小步数。

bfs的性质  

 bfs之所以称之为广度优先搜索,因为它的拓展方式是层层拓展,这保证了第一次搜索到一个点时,从起点到这个点的步数一定时最小的。(该博客从实际应用角度出发,不给出严格的数学证明  

,通过简单的反证和或者画图即可证明或者理解(其实是太懒了,qwq,大家自己画个图就可以理解了 

博主在开始学习bfs的时候也没有去证明bfs,我认为先大致理解并在题目上快速使用才会更好地保持长久的兴趣应该不会是博主不会吧,如果现阶段已经无法理解,建议自己画个图,画一个队列,模拟一下上题中的点是如何拓展的。

数组上的准备 

1.队列

struct Node{
    int x,y,step;
}que[N];

 因为我们要同时保存横坐标和纵坐标 以及最小步数,所以这里要使用结构体

2.标记数组

bool book[N][N];

3.方向数组(用于拓展)

int ne[4][2];//分别对应上下左右四个方向
ne[0][0] = 0,ne[0][1] = 1;
ne[1][0] = 0,ne[1][1] = -1;
ne[2][0] = 1,ne[2][1] = 0;
ne[3][0] = -1,ne[3][1] = 0;

 当然熟练以后我个人建议这样写

int ne[4][2] = {0,1,1,0,0,-1,-1,0};

实际上把0和1(或-1)放在相邻位置保证不重复即可,不用管顺序

这样前戏就差不多了

关于bfs一些细节上的代码 

1.怎么拓展 

int tx = temp.x+ne[i][0];//往四周走点
int ty = temp.y+ne[i][1];

 2.走到一个点后判断一个点是否合法(比如这个点有没有障碍,是否走出图的边界)

if(book[tx][ty]==true||tx<1||ty<1||tx>n||ty>m||g[tx][ty]==1)continue;

3.对于已经加入过队列的点要进行标记

book[tx][ty] = true;

bfs完整代码实现

#include<iostream>
using namespace std;

const int MAXN =110;

int g[MAXN][MAXN];
int res;
struct node{
    int x,y,step;
}que[MAXN*MAXN];
int head,tail;
bool book[MAXN][MAXN];
int n,m;
void bfs()
{
    head = 0;
    tail = -1;
    que[++tail].x = 1;
    que[tail].y = 1;
    que[tail].step = 0;
    int ne[4][2] = {1,0,0,1,0,-1,-1,0};
    while(tail>=head)
    {
        auto temp = que[head++];
        if(temp.x==n&&temp.y==m)//如果搜索到终点,就返回步数
        {
            res = temp.step;
            return;
        }
        for(int i = 0 ; i < 4 ; i++)
        {
            int tx = temp.x+ne[i][0];//往四周走点
            int ty = temp.y+ne[i][1];
            if(book[tx][ty]==true||tx<1||ty<1||tx>n||ty>m||g[tx][ty]==1)continue;//判断走到的点是否合法
            que[++tail].x = tx;//合法就加入到队列中
            que[tail].y = ty;
            que[tail].step = temp.step+1;
            book[tx][ty] = true;
        }
    }
    res = -1;
}

int main()
{
    cin>>n>>m;
    for(int i = 1 ; i <= n ; i++)
    {
        for(int j = 1; j <= m ; j++)
        {
            cin>>g[i][j];
        }
    }
    bfs();
    cout<<res;
    return 0;
}

大家可以看出,bfs的代码并不长,代码也比较简洁,所以方向大胆地去写吧 。

 一些习题

 P1162 填涂颜色 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

P1443 马的遍历 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 bfs的拓展应用

开始拓展之前说明几点:

  1. 博主在开始学习bfs的时候也没有去证明bfs,我认为先大致理解并在题目上快速使用才会更好地保持长久的兴趣应该不会是博主不会吧(再次说明绝不是因为心虚
  2. 证明也许在不久的将来会给出,读者也可以自己去翻别的博客
  3. 目前来看这里这里主要是挖坑,bfs的内容非常之多,这里也只能简单地介绍一下,后面可能会陆续更新,所以对于下面的问题,本身描述可能就不太清晰,所以仅仅是简单的做一下思维上的拓展

 从数组准备中延申出的拓展

我们一一回顾一下bfs前的准备:

  1.  队列
  2. 标记数组
  3. 方向数组

由此我们对于bfs有了三个思考方向 :

  1. 怎么在队列中维护更多的信息
  2. 如果题目中增加了一种条件,血量,在走动过程中涉及了血量变化,这又如何处理。
  3. 怎么处理新的拓展方式,比如一次性走两步

 从图的本身去做思考

 我们开篇就提到图中的点和边不一定要是具体

比如现在有这么一题:

给定一个数x,你需要进行若干次操作,使之变为y,求出操作的最小次数,无解输出-1

现在有两种操作:

  1. 给原来的数乘a
  2. 给原来的数加b

 那么这是点可能就是很多的数,边就是两个操作。

最后的一点总结 

很遗憾,没有在bfs最关键的点上说明得很清楚,原本我以为这是一件很简单的事,可事实上我无法表达,只能把它的理解交给读者,这是我本篇博客最大的遗憾。

关于上面所提及的拓展,每一个点都可以做成至少一篇博客,如果想了解更多,可以先关注我,我会在未来的一段时间持续更新。

感谢你们的支持。

  • 24
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值