目录
BFS广度优先搜索
理解
BFS与DFS虽然同为暴力算法,但bfs与dfs还是有一定的差距,dfs是一条路走到黑,沿着一条路径一直向下搜索,直到没有下一步,再回溯到上一个节点继续向下搜索;而bfs是把每一个点的分节点搜索完再继续搜索下一层(后面会详细讲),到找到时会通过一条最优路回溯回去。就像走迷宫,dfs是一个人走,而bfs是一群人走。
引入
可以先看一下这两个问题,浅浅的思考一下:
可以发现,bfs主要以解决最小最少最短一类的问题,这就是bfs于dfs的一大特征。(关于第二个问题中的线性筛素数,本萌新之后会写一篇文章来表达一下我的理解)
图解
我们以红色为起点,绿色为终点,黄色为已访问过的点。
搜索第一层
搜索第二层
搜索第三层
搜索第四层并找到终点
相关知识
在bfs中,有一个必要的工具——队列。队列分为两种:手工和STL(不熟悉的话可以看一下我之前写的介绍队列的文章)
bfs中的队列有着先进先出的特性,图解:
1进队列
1出队列,2,3进队列
2,3出队列,4,5,6进队列
4,5,6出队列
有趣的解释 (引用)
模板
跟dfs一样,bfs也有模板:
void bfs()
{
初始化,初始状态存数组
int front=0,rear=1,q[max_size]//构建队列
标记初始点位
while(front<rear)
{
front++;//指向带扩展结点
for(int i=1;i<=maxi;i++)
{
if(满足条件||不重复)
{
rear++;
将新结点压入队列;
}
}
}
}
e.g. 1:
爱与愁大神买完东西后,打算坐车离开中山路。现在爱与愁大神在 x1,y1 处,车站在 x2,y2 处。现在给出一个 n×n(n≤1000) 的地图,0 表示马路,1 表示店铺(不能从店铺穿过),爱与愁大神只能垂直或水平着在马路上行进。爱与愁大神为了节省时间,他要求最短到达目的地距离(每两个相邻坐标间距离为 1)。你能帮他解决吗?
输入格式
第 1 行包含一个数 n。
第 2 行到第 n+1 行:整个地图描述(0 表示马路,1 表示店铺,注意两个数之间没有空格)。
第 n+2 行:四个数 x1,y1,x2,y2。
输出格式
只有 1 行,即最短到达目的地距离。
3
0 0 1
1 0 1
1 0 0
1 1 3 3
4
5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
1 1 5 5
8
(分析+图解+思维)
这道题是求最短的问题,自然想到bfs(因为bfs是一格一格的向外拓展,当遇到终点时一定会有一个最优方案)
我们可以先定义两个二维数组,一个用来存maps,一个用来存最短距离d。
我们可以先将地图中的数字全看为-1:
注:(红色为最短距离,其他颜色为分支拓展,接下来将以数据2展开讲解)
我们从(0,0)开始搜索,具体如以下图示:
最后我们就得到了最短距离——8。
参考代码
#include<iostream>
#include<queue>
using namespace std;
struct node//结构体
{
int x,y;
};
queue <node> q;
int d[1100][1100];
int n,x,y,tx,ty;
int sa,sb,ta,tb;
const int dx[]={1,-1,0,0};
const int dy[]={0,0,1,-1};
char maps[1100][1100];
bool vis[1100][1100];
int bfs(int sx,int sy)
{
q.push((node){sx,sy});
vis[sx][sy]=true;
while(q.size())
{
x=q.front().x;
y=q.front().y;
q.pop();
if(x==ta&&y==tb)
return d[x][y];
for(int i=0;i<4;i++)
{
tx=x+dx[i];
ty=y+dy[i];
if(tx<=0||tx>n||ty<=0||ty>n)//边界
continue;
if(maps[tx][ty]=='1'||vis[tx][ty]==true)
continue;
d[tx][ty]=d[x][y]+1;//实现上面图解操作
vis[tx][ty]=true;
q.push((node){tx,ty});
}
}
return -1;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>maps[i][j];
cin>>sa>>sb>>ta>>tb;
cout<<bfs(sa,sb);
return 0;
}
e.g. 2:
巫妖王的天灾军团终于卷土重来,血色十字军组织了一支先锋军前往诺森德大陆对抗天灾军团,以及一切沾有亡灵气息的生物。孤立于联盟和部落的血色先锋军很快就遭到了天灾军团的重重包围,现在他们将主力只好聚集了起来,以抵抗天灾军团的围剿。可怕的是,他们之中有人感染上了亡灵瘟疫,如果不设法阻止瘟疫的扩散,很快就会遭到灭顶之灾。大领主阿比迪斯已经开始调查瘟疫的源头。原来是血色先锋军的内部出现了叛徒,这个叛徒已经投靠了天灾军团,想要将整个血色先锋军全部转化为天灾军团!无需惊讶,你就是那个叛徒。在你的行踪败露之前,要尽快完成巫妖王交给你的任务。军团是一个 n 行 m 列的矩阵,每个单元是一个血色先锋军的成员。感染瘟疫的人,每过一个小时,就会向四周扩散瘟疫,直到所有人全部感染上瘟疫。你已经掌握了感染源的位置,任务是算出血色先锋军的领主们感染瘟疫的时间,并且将它报告给巫妖王,以便对血色先锋军进行一轮有针对性的围剿。
输入格式
第 1 行:四个整数 n,m,a,b,表示军团矩阵有 n 行 m 列。有 a 个感染源,b 为血色敢死队中领主的数量。
接下来 a 行:每行有两个整数 x,y,表示感染源在第 x 行第 y 列。
接下来 b 行:每行有两个整数 x,y,表示领主的位置在第 x 行第 y 列。
输出格式
第 1 至 b 行:每行一个整数,表示这个领主感染瘟疫的时间,输出顺序与输入顺序一致。如果某个人的位置在感染源,那么他感染瘟疫的时间为 0。
图解
红色为感染源,蓝色为领主。
参考代码
#include<iostream>
#include<cstring>
#define x first
#define y second
using namespace std;
typedef pair<int,int> P;//存坐标
int n,m,a,b;
int d[1000][1000];
P q[300000];//手工队列
int front=0,rear=-1;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
void bfs()
{
while(front<=rear)
{
P t=q[front++];
for(int i=0;i<4;i++)
{
int a=t.x+dx[i];
int b=t.y+dy[i];
if(a<1||a>n||b<1||b>m)
continue;
if(d[a][b]>=0)
continue;
d[a][b]=d[t.x][t.y]+1;
q[++rear]={a,b};
}
}
}
int main()
{
cin>>n>>m>>a>>b;
memset(d,-1,sizeof d);
while(a--)
{
int g,h;
scanf("%d%d",&g,&h);
q[++rear]={g,h};
d[g][h]=0;
}
bfs();
while(b--)
{
int g,h;
scanf("%d%d",&g,&h);
cout<<d[g][h]<<endl;
}
}
e.g. 3:
贝茜听说一场特别的流星雨即将到来:这些流星会撞向地球,并摧毁它们所撞击的任何东西。她为自己的安全感到焦虑,发誓要找到一个安全的地方(一个永远不会被流星摧毁的地方)。
如果将牧场放入一个直角坐标系中,贝茜现在的位置是原点,并且,贝茜不能踏上一块被流星砸过的土地。
根据预报,一共有 M 颗流星 (1≤M≤50,000) 会坠落在农场上,其中第 i 颗流星会在时刻 Ti(0≤Ti≤1000)砸在坐标为 (Xi,Yi)(0≤Xi≤300,0≤Yi≤300) 的格子里。流星的力量会将它所在的格子,以及周围 4 个相邻的格子都化为焦土,当然贝茜也无法再在这些格子上行走。
贝茜在时刻 0 开始行动,她只能在第一象限中,平行于坐标轴行动,每 1 个时刻中,她能移动到相邻的(一般是 4 个)格子中的任意一个,当然目标格子要没有被烧焦才行。如果一个格子在时刻 t 被流星撞击或烧焦,那么贝茜只能在 t 之前的时刻在这个格子里出现。 贝茜一开始在 (0,0)。
请你计算一下,贝茜最少需要多少时间才能到达一个安全的格子。如果不可能到达输出 −1。
输入格式
共 M+1 行,第 1 行输入一个整数 M,接下来的 M 行每行输入三个整数分别为 Xi,Yi,Ti。
输出格式
贝茜到达安全地点所需的最短时间,如果不可能,则为 −1。
图解
下面两幅图需要对照着看
贝茜从第0时刻开始逃生,结合两张图可以看出,贝茜在逃生时只能走红色区域,如果走其他区域,那么贝茜在2秒后都会死于流星(需要剪枝的情况),最后,贝茜在到达绿色安全区域时所需的最短时间为5。
参考代码
#include<iostream>
#include<cstring>
#define x first
#define y second
using namespace std;
typedef pair<int,int> P;
int m;
int d[500][500];
int f[500][500];
P q[100000];
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int bfs()
{
q[0]={0,0};
d[0][0]=0;
int front=0,rear=0;
while(front<=rear)
{
P t=q[front++];
for(int i=0;i<4;i++)
{
int a=t.x+dx[i];
int b=t.y+dy[i];
if(a<0||b<0)
continue;
if(d[a][b])
continue;
if(d[t.x][t.y]+1>=f[a][b])
continue;
d[a][b]=d[t.x][t.y]+1;
q[++rear]={a,b};
if(f[a][b]>1e9)
return d[a][b];
}
}return -1;
}
int main()
{
scanf("%d",&m);
memset(f,0x3f,sizeof f);
while(m--)
{
int g,h,t;
cin>>g>>h>>t;
f[g][h]=min(t,f[g][h]);
for(int i=0;i<4;i++)
{
int a=g+dx[i];
int b=h+dy[i];
if(a<0||b<0||a>300||b>300)
continue;
f[a][b]=min(t,f[a][b]);
}
}
int ans=bfs();
printf("%d",ans);
}
推荐题目
洛谷:1443,1162,1747,2658,2730
原手稿
推荐大家平时做题也多写多画