一、BFS的介绍
BFS(广度优先搜索,也可称宽度优先搜索)是连通图的一种遍历策略。因为它的基本思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域。
广度优先搜索(BFS)类似于二叉树的层序遍历算法,它的基本思想是:首先访问起始顶点v,然后由v出发,依次访问v的各个未被访问过的邻接顶点w1,w2,w3….wn,然后再依次访问w1,w2,…,wi的所有未被访问过的邻接顶点,再从这些访问过的顶点出发,再访问它们所有未被访问过的邻接顶点….以此类推,直到途中所有的顶点都被访问过为止。类似的想法还将应用与Dijkstra单源最短路径算法和Prim最小生成树算法。
广度优先搜索是一种分层的查找过程,每向前走一步可能访问一批顶点,不像深度优先搜索(DFS)那样有回退的情况,因此它不是一个递归的算法,为了实现逐层的访问,算法必须借助一个辅助队列并且以非递归的形式来实现。
二、BFS搜索的步骤
1、首先创建一个visit[ ]数组和一个队列q,分别用来判断该位置是否已经访问过及让未访问过的点入队;
2、初始化visit[ ]数组,清空q队列;
3、让起点start入队,并使该点的visit置1;
4、while(!q.empty()){......}执行搜索操作,
a、取出队头元素后使队头元素出队,判断该元素是否为目标到达点;
b、如果是目标点,就返回结果(一般是最短时间、最短路径);
c、如果不是目标点,就继续访问与其相邻的位置点,将可走的相邻的位置点入队,并更新visit[ ]数组;
三、BFS的应用
BFS算法一般应用于单源最短路径的搜索。
1、寻找非加权图(或者所有边权重相同)中任两点的最短路径。
2、寻找其中一个连通分支中的所有节点。(扩散性)3、bfs染色法判断是否为二分图。
四、核心代码
#include<iostream>
#include<queue>
#include<string.h>
#define maxn 105
using namespace std;
int n,m; //矩阵的大小
int sx,sy;
int vis[maxn][maxn],s[maxn][maxn],t[maxn][maxn];
queue<struct node>Q;
int px[]={1,-1,0,0}; //人可走的4个方向
int py[]={0,0,1,-1};
struct node{
int x,y,step;
}r,p,q;
int BFS()
{
//清空队列及初始化vis数组
while(!Q.empty())
Q.pop();
memset(vis,0,sizeof(vis));
p.x=sx;
p.y=sy;
p.step=0;
vis[p.x][p.y]=1;
Q.push(p);
while(!Q.empty())
{
p=Q.front();
Q.pop();
if(s[p.x][p.y]=='t')
return p.step;
for(int i=0;i<4;i++)
{
q=p;
q.x+=px[i];
q.y+=py[i];
q.step++;
if(q.x<0||q.y<0||q.x>=n||q.y>=m)
continue;
//访问未被访问过的位置,且此时是在火势蔓延到此前访问的,再将该位置入队
if(vis[q.x][q.y]==0&&q.step<t[q.x][q.y])
{
vis[q.x][q.y]=1;
Q.push(q);
}
}
}
return -1;
}
int main(){
//功能需求代码
//........
return 0;
}
五、例题
【奇怪的电梯】
-题目描述-
有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第i层楼(1<=i<=N)上有一个数字Ki(0<=Ki<=N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如:3 3 1 2 5代表了Ki(K1=3,K2=3,……),从一楼开始。在一楼,按“上”可以到4楼,按“下”是不起作用的,因为没有-2楼。那么,从A楼到B楼至少要按几次按钮呢?点击打开链接
-输入格式-
共二行。第一行为 3 个用空格隔开的正整数,表示 N,A,B(1≤N≤200, 1≤A,B≤N)N,A,B(1≤N≤200,1≤A,B≤N) 。第二行为 N 个用空格隔开的非负整数,表示 K_i。
-输出格式-
一行,即最少按键次数,若无法到达,则输出 -1−1 。
-样例数据-
input
5 1 5
3 3 1 2 5
output
3
-分析-
这道题所要求的是最小值(最少要按的按钮数)。用BFS找到答案便可以退出(DFS不能保证答案为最优解)。要注意边界:是否可以继续往上或向下(没有0楼,-1楼......)。
-代码-
#include<bits/stdc++.h>
using namespace std;
int n,a,b;
int que[222]={};
int k[222]={};
int vis[222]={};//标记是否询问过
int tmp[222]={};
int head=1,tail=0;//队首队尾指针
void bfs(int now)
{
for(;head<=tail;)//当队列不为空时
{
int top=que[head];//top为队首的层数
head++;//弹出队首
if(top==b)
return ;//假如已经到达了目标(b)层,直接退出
if(top+k[top]<=n&&vis[top+k[top]]==0)//假如没有超出边界并且没有访问过
{
que[++tail]=top+k[top];//入队
tmp[top+k[top]]=tmp[top]+1;//次数为上一次+1
vis[top+k[top]]=1;//标记
}
if(top-k[top]>=1&&vis[top-k[top]]==0)
{
que[++tail]=top-k[top];
tmp[top-k[top]]=tmp[top]+1;
vis[top-k[top]]=1;
}
}
return ;
}
int main()
{
cin>>n>>a>>b;
for(int i=1;i<=n;i++)
cin>>k[i];
que[++tail]=a;
vis[a]=1;
bfs(a);
if(vis[b]==0)//假如没有访问过
cout<<-1<<endl;
else//访问过
cout<<tmp[b]<<endl;
return 0;
}
【青铜莲花池】
-题目描述-
为了让奶牛们娱乐和锻炼,农夫约翰建造了一个美丽的池塘。这个长方形的池子被分成了 M 行 N 列个方格(1 ≤ M, N ≤ 30) 。一些格子是坚固得令人惊讶的莲花,还有一些格子是岩石,其余的只是美丽、纯净、湛蓝的水。贝西正在练习芭蕾舞,她站在一朵莲花上,想跳到另一朵莲花上去,她只能从一朵莲花跳到另一朵莲花上,既不能跳到水里,也不能跳到岩石上。贝西的舞步很像象棋中的马步:每次总是先横向移动 M1 (1 ≤ M1 ≤ 30)格,再纵向移动 M2 (1 ≤ M2 ≤ 30, M1≠M2)格,或先纵向移动 M1 格,再横向移动 M2 格。最多时,贝西会有八个移动方向可供选择。给定池塘的布局和贝西的跳跃长度,请计算贝西从起点出发,到达目的地的最小步数,我们保证输入数据中的目的地一定是可达的。点击打开链接
-输入格式-
第一行:四个用空格分开的整数:M,N,M1 和 M2第二行到 M + 1 行:第 i + 1 行有 N 个用空格分开的整数,描述了池塘第i 行的状态:0 为水,1 为莲花,2 为岩石,3 为贝西所在的起点,4 为贝西想去的终点。
-输出格式-
一行,从起点到终点的最少步数。
-样例数据-
input
4 5 1 2
1 0 1 0 1
3 0 2 0 4
0 1 2 0 0
output
2
-分析-
贝西的跳跃方式像马(马按“日”字形走(点击打开链接))。只要列出八个方向,判断这几个方向是否可以进行跳跃(跳跃到的位置必须有莲叶且先前没有进行过标记),如果可以则入队,不断弹出队首,直至队列为空或者跳到目标位置,跳出循环。
-代码-
#include<bits/stdc++.h>
using namespace std;
int m,n,m1,m2;
int a[33][33]={};
int vis[33][33]={};
int ans[33][33]={};
int lx,ly;
struct kk
{
int x;
int y;
} que[3333]={};
int head=1,tail=0;
int bfs()
{
for(;head<=tail;)
{
int x=que[head].x;
int y=que[head].y;//记录队首的坐标
head++;//弹出队首
if(x==lx&&y==ly)
return ans[x][y];//返回答案
if(x+m1<=m&&y+m2<=n&&vis[x+m1][y+m2]==0&&a[x+m1][y+m2]==1)
{
que[++tail]=(kk){x+m1,y+m2};
vis[x+m1][y+m2]=1;
ans[x+m1][y+m2]=ans[x][y]+1;
}
if(x-m1>0&&y-m2>0&&vis[x-m1][y-m2]==0&&a[x-m1][y-m2]==1)
{
que[++tail]=(kk){x-m1,y-m2};
vis[x-m1][y-m2]=1;
ans[x-m1][y-m2]=ans[x][y]+1;
}
if(x-m1>0&&y+m2<=n&&vis[x-m1][y+m2]==0&&a[x-m1][y+m2]==1)
{
que[++tail]=(kk){x-m1,y+m2};
vis[x-m1][y+m2]=1;
ans[x-m1][y+m2]=ans[x][y]+1;
}
if(x+m1<=m&&y-m2>0&&vis[x+m1][y-m2]==0&&a[x+m1][y-m2]==1)
{
que[++tail]=(kk){x+m1,y-m2};
vis[x+m1][y-m2]=1;
ans[x+m1][y-m2]=ans[x][y]+1;
}
if(x+m2<=m&&y+m1<=n&&vis[x+m2][y+m1]==0&&a[x+m2][y+m1]==1)
{
que[++tail]=(kk){x+m2,y+m1};
vis[x+m2][y+m1]=1;
ans[x+m2][y+m1]=ans[x][y]+1;
}
if(x-m2>0&&y-m1>0&&vis[x-m2][y-m1]==0&&a[x-m2][y-m1]==1)
{
que[++tail]=(kk){x-m2,y-m1};
vis[x-m2][y-m1]=1;
ans[x-m2][y-m1]=ans[x][y]+1;
}
if(x-m2>0&&y+m1<=n&&vis[x-m2][y+m1]==0&&a[x-m2][y+m1]==1)
{
que[++tail]=(kk){x-m2,y+m1};
vis[x-m2][y+m1]=1;
ans[x-m2][y+m1]=ans[x][y]+1;
}
if(x+m2<=m&&y-m1>0&&vis[x+m2][y-m1]==0&&a[x+m2][y-m1]==1)
{
que[++tail]=(kk){x+m2,y-m1};
vis[x+m2][y-m1]=1;
ans[x+m2][y-m1]=ans[x][y]+1;
}
}//八个方向进行跳跃
}
int main()
{
cin>>m>>n>>m1>>m2;
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
cin>>a[i][j];
if(a[i][j]==3)
{
que[++tail]=(kk){i,j};
vis[i][j]=1;
}//如果为初始位置,入队,并进行标记
if(a[i][j]==4)
{
lx=i;
ly=j;
a[i][j]=1;//为了方便,这样只需要判断a[i][j]为1时可以跳跃
} //记录结束位置
}
cout<<bfs()<<endl;//bfs
return 0;
}