大家好,我是清墨,欢迎收看《C++进阶课程——宽度优先搜索(BFS)》。

啊,上一期我们的情况啊也是非常好的,今天直接开始!
例题——小老鼠走迷宫
我们先从例题开始。
题目描述
一个M*N的迷宫矩阵由 0 和 1 组成,1 表示墙壁,0 表示通路。
一只小老鼠从左上角即坐标(0,0)出发,只能走上下左右四个方向(不能走斜线),问小老鼠能否吃到右下角出口即坐标(M-1,N-1)处的奶酪。
输入格式
第一行输入空格分开的两个整数,表示迷宫的行数和列数。
然后输入M行N列的迷宫矩阵。
数据范围
5 <= M,N <= 50
输出格式
若能走到出口,输出"yes",否则输出"no"
样例
输入数据 1
5 5
0 0 1 0 1
0 0 1 0 0
0 1 0 1 1
0 1 0 0 0
0 0 0 0 0
输出数据1
yes
输入数据 2
5 5
0 0 1 0 1
0 0 1 0 0
0 1 0 1 1
0 1 1 0 0
0 0 1 0 0
输出数据 2
no
讲解
这种就是我们BFS算法里一个经典的小专题,叫做泛洪算法。
说了那么多,先讲讲BFS。
BFS全称Breadth-First Search(广度优先搜索),是一种用于遍历或搜索树或图数据结构的算法。它从指定节点开始,逐层访问所有相邻节点,直到找到目标或遍历完整张图。
这道题看起来很复杂,可我们不妨把上面的地图做一些标记(我们拿样例1作为例子),我们让每一个格子都赋 1 个名称,用 A/B/C/D.....来标记(哈哈哈,幸好只有 25 个格子,所以用 1 个字母就可以标识了!)。然后,我们用绿色的格子来表示可以走到格子,用红色格子用来表示障碍格子。得到下图:

1.刚开始从A点出发,可以到两个点。
由题意得,从(0,0)出发,即A点。
而如果从A点出发,从A往外探索,可以找到B、F两个点。

2. 上一轮新找到的点是 B 和 F,可以找到两个点。
从 B 和 F 往外探索,各自找到 1 个儿子:G 和 K 。

我们假定找儿子的顺序是 右-下-左-上,所以 G 成了 B 的儿子,然后就不把 G 看作 F 的儿子了(重复计算没有任何好处)。如果改变查找的顺序,会得到略有不同的情况,但是算法的意思不变,最终的计算结果不变。
3.上一轮新找到的点是 G 和 K,从 G 和 K 往外探索。G 找到了 1 个儿子:P; K 没有儿子。

4.重复上述的搜索方法,最终搜索结果为:

所以,在第 8 步的时候找到了迷宫的出口 Y 。
如果我们把搜索到的点按照顺序连起来,会是下图这样:

上面这个按层拓展搜索的方法,就是 BFS 算法的思想了。我们可以把构建一个点的队列,不断的把新找到的点放在队列的尾部,不断的从队列的头部把点拿出来,继续搜索,从而把上面的搜索思想转变成程序。
程序主体
#define xx t.x + diff[k][0]
#define yy t.y + diff[k][1]
struct point{
int x,y;
};
queue <point> q;//队列,革命的种子,存放着搜寻到的点
int diff[4][2] = {{0,1},{0,-1},{1,0},{-1,0}};
bool bfs(){
vis[0][0] = true;
q.push( {0,0} );
//上面两行代码很重要
point t;
while(q.size())
{
t = q.front();
q.pop();
if(t.x==n-1&&t.y==m-1)
{
return true;
}//找到了出口
for(int k=0;k<4;k++)
{
//循环四次分别为四个方向
if(xx<n&&xx>=0&&yy<m&&yy>=0&&!vis[xx][yy]&&v[xx][yy]==0)
{
//判断边界+判断是否经过过
//把探索到的点都加入队列,打上标记
q.push( {xx, yy} );
vis[xx][yy] = true;
}
}
}
//遍历完还没找到
return false;
}
练一练
这种简单的题你已经看过了,那我们增加难度,嘻嘻。
桂城真题
题目描述
您正在玩一个游戏,给出一个地图,地图实际是一个 R 行 C 列的二维字符数组。'#' 表示障碍物,'.' 表示可通行格子,'v' 表示飞机。游戏中只有 1 架飞机,而且在第 1 行。
游戏有 3 个键:向左键、向右键、下降键。其中向左键可以把飞机向左移动一个格子,向右键可以把飞机向右移动一个格子,下降键可以把飞机向下移动一个格子。每按一次向左键需要消耗 1 个能量,每按一次向右键需要消耗 1 个能量,每按一次下降键需要消耗 0 个能量。
有一个特殊规定:对于地图的任意一行,飞机在该行所消耗的能量都不能超过 K。
你的任务是控制飞机降落,顺利到达第 R 行,不能碰到障碍物,而且消耗的总能量最少。
例如下图,R=7, C=9, K=2:
##..v..##
###.....#
#####...#
####...##
###..####
#.......#
#...#####
其中一种最优的方案是:
- 在第一行按“向右键”、“下降键”
- 在第二行按“下降键”
- 在第三行按“下降键”
- 在第四行按“向左键”、“下降键”
- 在第五行按“向左键”、“下降键”
- 在第六行按“下降键”
- 顺利到达最后一行。
总共消耗 3 个能量,已经是最优解了。
输入格式
第一行,3 个整数:R、C、K。 1≤R≤50, 3≤C≤50, 0≤K≤47。
接下来是 R 行 C 列的二维地图。
对于地图的每一行来说,开头都是连续的若干个障碍物格子,结尾也是连续的若干个障碍物格子,中间是连续的可通行格子(看题干描述的例子)。第一行比较特殊,因为有驾飞机,所以有一个可通行格子被 'v' 替代了。
输出格式
一个整数,顺利完成任务所需要消耗的最少能量。如果不能完成任务,输出 -1。
分析
这道题,要怎样与宽度优先搜索融合呢?
在观察中思考。
此处搜索的方式有所不同,可以把它想象成一把刀。(如下图所示)
A点为飞机所在的地方,B点为A点向左或向右所达到的目标点,C点为B点的下一层的同列格。(sorry,这里图画得不好,线段BC画太长了)
而我们while循环里的for循环也该进行一定的修改:
①多加一层循环
有两个方向,分别为左和右,所以外层for循环循环次数为2次。
②第二层for循环循环次数为k次
每一行最多可左右移动k次
③添加能量数组
为了减少队列的元素,也为了减少没有用的方法,我们可以定义一个ans数组,用来存储到该层所用的最少能量。
④if语句的修改
随着循环次数的改变,以及数组的增加,还有障碍物的加入(由题意),我们应该多加几个if语句,首先要判断边界,其次判断此处是否有障碍物,最后判断所用的能量有没有减少。(只有减少了,本次搜索才有意义,我们为的是让能量使用尽可能少)
⑤其他
判断后,不仅要将该位置放进队列,还要把能量进行刷新。
for(int dir=0;dir<2;dir++)
{
for(int i=0;i<=k;i++)
{
if(cc&&cc<=c&&t.r<r)
{
if(v[t.r][cc]=='.'||v[t.r][cc]=='v')
{
if(v[t.r+1][cc]=='.'&&ans[t.r+1][cc]>ans[t.r][t.c]+i)
{
q.push({t.r+1,cc});
ans[t.r+1][cc]=ans[t.r][t.c]+i;
}
}
else break;
}
}
}
大概就是这样了,其他小细节,比如说用字符数组存储之类的,可以自己完成。
代码实现
#include<bits/stdc++.h>
using namespace std;
#define cc t.c+diff[dir]*i
int diff[2]={-1,1};
int ans[52][52],r,c,k;
string v[51];
struct point
{
int r,c;
};
void bfs(int p)
{
memset(ans,0x3f,sizeof(ans));
queue<point>q;
q.push({1,p});
ans[1][p]=0;
point t;
while(!q.empty())
{
t=q.front();
q.pop();
for(int dir=0;dir<2;dir++)
{
for(int i=0;i<=k;i++)
{
if(cc&&cc<=c&&t.r<r)
{
if(v[t.r][cc]=='.'||v[t.r][cc]=='v')
{
if(v[t.r+1][cc]=='.'&&ans[t.r+1][cc]>ans[t.r][t.c]+i)
{
q.push({t.r+1,cc});
ans[t.r+1][cc]=ans[t.r][t.c]+i;
}
}
else break;
}
}
}
}
}
int minn=0x3f3f3f;
int main(){
cin>>r>>c>>k;
for(int i=1;i<=r;i++)
{
cin>>v[i];
}
bfs(v[1].find('v'));//寻找初始时飞机的位置
for(int i=1;i<=c;i++)
{
minn=min(minn,ans[r][i]);
}
if(minn!=0x3f3f3f)cout<<minn;//如果到达不了才会还是无穷大
else cout<<-1;
return 0;
}

&spm=1001.2101.3001.5002&articleId=149118748&d=1&t=3&u=781bd558411f4793a4fbd84513346137)
705

被折叠的 条评论
为什么被折叠?



