目录
1.BFS的定义BFS(广度优先搜索,也可称宽度优先搜索)是连通图的一种遍历策略。因为它的基本思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域。
2.BFS搜索的步骤1、首先创建一个visit[ ]数组和一个队列q,分别用来判断该位置是否已经访问过及让未访问过的点入队;
4.BFS的特点BFS的特点是先访问距离起始节点近的节点,然后逐渐向离起始节点更远的节点进行扩展。因此,当在图中搜索最短路径或寻找最近邻节点时,BFS是一种常用的算法。
一、BFS的介绍
1.BFS的定义
BFS(广度优先搜索,也可称宽度优先搜索)是连通图的一种遍历策略。因为它的基本思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域。
广度优先搜索(BFS)类似于二叉树的层序遍历算法,它的基本思想是:首先访问起始顶点v,然后由v出发,依次访问v的各个未被访问过的邻接顶点w1,w2,w3….wn,然后再依次访问w1,w2,…,wi的所有未被访问过的邻接顶点,再从这些访问过的顶点出发,再访问它们所有未被访问过的邻接顶点….以此类推,直到途中所有的顶点都被访问过为止。类似的想法还将应用与Dijkstra单源最短路径算法和Prim最小生成树算法。
广度优先搜索是一种分层的查找过程,每向前走一步可能访问一批顶点,不像深度优先搜索(DFS)那样有回退的情况,因此它不是一个递归的算法,为了实现逐层的访问,算法必须借助一个辅助队列并且以非递归的形式来实现。
为了更加直观的体现,请看如图,DFS先是一个地方搜到底,一个点一个点地搜索,而BFS是全方面搜索,一层一层的搜索,更为直观,全面。
2.BFS搜索的步骤
1、首先创建一个visit[ ]数组和一个队列q,分别用来判断该位置是否已经访问过及让未访问过的点入队;
2、初始化visit[ ]数组,清空q队列;
3、让起点start入队,并使该点的visit置1;
4、while(!q.empty()){......}执行搜索操作,
a、取出队头元素后使队头元素出队,判断该元素是否为目标到达点;
b、如果是目标点,就返回结果(一般是最短时间、最短路径);
c、如果不是目标点,就继续访问与其相邻的位置点,将可走的相邻的位置点入队,并更新visit[ ]数组;
3.BFS的应用
BFS算法一般应用于单源最短路径的搜索。
1、寻找非加权图(或者所有边权重相同)中任两点的最短路径。
2、寻找其中一个连通分支中的所有节点。(扩散性)
3、bfs染色法判断是否为二分图。
4.BFS的特点
BFS的特点是先访问距离起始节点近的节点,然后逐渐向离起始节点更远的节点进行扩展。因此,当在图中搜索最短路径或寻找最近邻节点时,BFS是一种常用的算法。
需要注意的是,BFS使用队列来存储待访问的节点,因此它是一种先进先出(First-In-First-Out,FIFO)的算法。此外,为了避免重复访问节点,需要使用一个标记数组或哈希表来记录节点的访问状态。
一般来说当图的边权都为1时使用BFS,边权为1表示从一个节点到相邻节点的距离或代价是相等的,这意味着每个相邻节点离起始节点的距离相差一个单位。在这种情况下,BFS可以确保首次到达目标节点时的路径长度最小。
如果图中存在边权不为1的情况,BFS可能无法得到最短路径。 对于带有不同边权的图,更适合使用Dijkstra算法或A*算法等其他路径搜索算法,它们可以考虑不同边权的影响,找到最优路径。
二,BFS的实战应用
1.BFS的大致模板如下:
1 BFS算法:
2
3 通常用队列(先进先出,FIFO)实现
4
5 初始化队列Q;
6 Q = {起点s};
7 标记s为已访问;
8 while(Q非空)
9 {
10 取Q队首元素u;
11 u出队;
12 if(u==目标状态)
13 {
14 ……
15 }
16 else
17 {
18 所有与u相邻且未被访问的点进入队列;
19 标记u为已访问;
20 }
21 }
1.题目:迷宫
输入n,m表示迷宫大小,在输入1或2表示能不能走,1表示能走,2表示不能走,在输入起点和终点,最后输出起点到终点要走多少步。
输入:
5 4
1 1 2 1
1 1 1 1
1 1 2 1
1 2 1 1
1 1 1 2
1 1 4 3
输出:
7
方法是bfs,然后加了一点点队列和结构体
#include<bits/stdc++.h>
using namespace std;
int a[100][100];//a【i】【j】=1表示是空地可以走,为0表示不是空地
int v[100][100];//v【i】【j】=1表示已经被访问了,为0表示未被访问
struct point {
int x;
int y;
int step;
};
queue<point> r;//定义队列
int dx[4]= {0,1,0,-1}; //四个方向
int dy[4]= {1,0,-1,0};
int main() {
//input
int n,m,startx,starty,p,q;
scanf("%d%d",&n,&m); //迷宫大小
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
scanf("%d",&a[i][j]); //输入1或2,如果是1表示可以走,2表示不能走
}
}
scanf("%d%d%d%d",&startx,&starty,&p,&q); //输入起点和终点
//BFS
point start;
//初始化开始
start.x = startx;
start.y = starty;
start.step = 0;
//初始化结束
r.push(start);//将起点入队
v[startx][starty] = 1 ;
int flag = 0;
while(!r.empty()) { //判断是不是空栈,是则值为1,不是则值为0
int x=r.front().x,y=r.front() .y;
if(x==p && y==q) { // arrive destinationa
flag = 1;
cout<<r.front().step;
break;
}
for(int k = 0 ; k < 3 ; k++) {
int tx,ty;
tx = x + dx[k];
ty = y + dy[k];
if (a[tx][ty]==1 && v[tx][ty]==0) { //如果是空地 并且 未被访问过
//入队
point temp;//把拓展的点放到temp里
temp.x = tx;
temp.y = ty;
temp.step = r.front().step + 1 ;
r.push(temp);
v[tx][ty] = 1; //设置为已访问
}
}
r.pop();//拓展完了需要将队首元素出队
}
if(flag==0)//没找到
cout<<"no answer";
return 0;
}
2.上题目:马的遍历
请读者思考一下应该怎么写,如果思路跟我一样,我记得可以照搬我的
输入:
3 3 1 1
输出:
0 3 2
3 -1 1
2 1 4
//马 队列
#include<bits/stdc++.h> //非本人写的,并且本人不是用这种方法写的
using namespace std;
const int dx[8]= {-1,-2,-2,-1,1,2,2,1};
const int dy[8]= {2,1,-1,-2,2,1,-1,-2}; //8个方向
queue<pair<int,int> >q; //queue指队列,pair的使用是为了让队列里有两个数据,正常情况下队列里只能有一个,现在也可以是 <string,int>q <char,double>q
int f[500][500];//存步数
bool vis[500][500];//走没走过
int main() {
int n,m,x,y;
memset(f,-1,sizeof(f));
memset(vis,false,sizeof(vis));
cin>>n>>m>>x>>y;
f[x][y]=0;
vis[x][y]=true;
q.push(make_pair(x,y));
while(!q.empty()) { //empty判断是不是空栈,说的话值为1,不是则为0
int xx=q.front().first,yy=q.front().second; //英语,first指第一个,second指第二个,
q.pop();//取队首并出队
for(int i=0; i<8; i++) {
int u=xx+dx[i],v=yy+dy[i];
if(u<1||u>n||v<1||v>m||vis[u][v])continue;//出界或走过就不走
vis[u][v]=true;
q.push(make_pair(u,v));
f[u][v]=f[xx][yy]+1;
}
}
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++)
printf("%-5d",f[i][j]);
printf("\n");
} //注意场宽!!
return 0;
}
3.流星雨S
输入:
4
0 0 2
2 1 2
1 1 2
0 3 5
输出:
5
#include<bits/stdc++.h> //万能头文件
using namespace std;
int n,ma[305][305],v[305][305],sx,sy,st,ans[305][305];//分别为陨石数量,陨石砸落地图,记录是否走过地图,陨石x,y坐标及砸落时间,每个点的最少时间图。
int dx[5]= {0,0,1,-1};
int dy[5]= {1,-1,0,0}; //方便移动和处理陨石砸落
int main() {
cin>>n;
for (int i=0; i<305; i++) {
for (int j=0; j<305; j++) {
ma[i][j]=-1;
}
}//陨石坠落地初始化为-1
for (int i=1; i<=n; i++) {
cin>>sx>>sy>>st;//输入陨石
for (int j=0; j<5; j++) { //上下左右中标记陨石
if (sx+dx[j]>=0&&sy+dy[j]>=0&&(ma[sx+dx[j]][sy+dy[j]]==-1||ma[sx+dx[j]][sy+dy[j]]>st))
ma[sx+dx[j]][sy+dy[j]]=st; //如果该标记x,y坐标大于0且该点没有被陨石砸落或已标记时间没有该时间早,标记陨石
}
}
queue<pair<int,int> >q; //构造队列,存储将处理点x,y坐标
v[0][0]=1; //初始点设为已走过
q.push(make_pair(0,0)); //初始点放入队列
while (!q.empty()) { //只要队列不为空
int x=q.front().first,y=q.front().second; //提取将处理点x,y坐标
q.pop(); //删除已处理点,出栈
int s=ans[x][y]+1; //即将标记的点时间是现在点的下一个单位
if (ma[x][y]==-1) { //如果该点安全,输出即将标记的点的时间-1
printf("%d",s-1);
return 0;
}
for (int i=0; i<4; i++) {
int xx=x+dx[i],yy=y+dy[i];//提取将处理点的坐标
if (xx>=0&&yy>=0&&(ma[xx][yy]==-1||s<ma[xx][yy])&&v[xx][yy]==0) { //将处理点需要x,y坐标大于等于0,且该点没有走过,并且陨石降落时间小于现时间
q.push(make_pair(xx,yy));//放入将处理队列
v[xx][yy]=1;//标记已走过
ans[xx][yy]=s;//将该点时间放入数组
}
}
}
printf("-1");//如果出不了陨石区,输出-1
return 0;
}
4.题目:玉米迷宫s
输入:
5 6
###=##
#.W.##
#.####
#.@W##
######
输出:
3
认真思考!
#include<bits/stdc++.h>
using namespace std;
const int N=350;
struct point {
int x;
int y;
int t;
};
queue<point> que;
char a[N][N];
bool vis[N][N];
int n,m;
int dx[4]= {1,0,-1,0};
int dy[4]= {0,1,0,-1};
int sx;
int sy;
void goto_another(int &nx,int &ny,int k) { //goto_another函数用于寻找另一个传送门,nx、ny代表当前点的坐标,记得要加上取地址符'&',因为每当贝西踏入一个传送门,它就会立即被传送至另一个传送门,不能在原地停留
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
if(a[i][j]==a[nx][ny]&&(i!=nx||j!=ny)) { //如果a[i][j]这个点的是一个与a[nx][ny]相同的传送门,并且a[i][j]与a[nx][ny]不是同一个点
nx=i;//改变当前坐标,将贝西强行移动至另一个传送门处
ny=j;
return ;//告辞
}
}
}
}
int main() {
cin>>n>>m;
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
cin>>a[i][j];
if(a[i][j]=='@') { //获取起点坐标
sx=i;
sy=j;
}
}
}
que.push((point) { //入列
sx,sy,0
});
while(!que.empty()) { //不是非空就继续循环
point f=que.front();//重新定义一个结构体的通用字f
que.pop(); //出列
if(a[f.x][f.y]=='=') { //终点,结束
cout<<f.t;
return 0;
}
if(a[f.x][f.y]>='A'&&a[f.x][f.y]<='Z') { //题目说范围是A到Z都可以用,如果当前点是一个传送门,那么就传送至另一个传送门
goto_another(f.x,f.y,f.t); //到达另一个传送门的地方
}
for(int i=0; i<=3; i++) { //四个方向
int nx=f.x+dx[i];
int ny=f.y+dy[i];
if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&a[nx][ny]!='#'&&!vis[nx][ny]) { //入列条件
vis[nx][ny]=true;
que.push((point) { //入列
nx,ny,f.t+1 //t用来统计步数
});
}
}
}
return 0;
}
5.题目:机器人搬重物
输入;
9 10
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 1 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 1 0 0 0 0
0 0 0 1 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 1 0
7 2 2 7 S
输出:
12
挺难的说。
#include<bits/stdc++.h>
using namespace std;
int sd[55][55];
int a[55][55];//a为读入的方格地图
int n,m;
int x11,y11;//起点
int x2,y2;//终点
int f[55][55];//f为格点地图
int fx[5]={0,-1,1,0,0};//fx[i]表方向i(编号)的x情况
int fy[5]={0,0,0,-1,1};//fy[i]表方向i(编号)的y情况
int ft[5]={0,1,4,2,3};//ft[i]表示顺时针排列各个方向的编号(上1 右4 下2 左3) 上加下=5,左加右=5
int fft[5]={0,1,3,4,2};//fft[i]表示数字i在ft[]数组中的下标 ,即不是数组ft[]此时的方向
int abc[5]={0,1,2,1,0};//abc[5]表示转到[顺时针转i次到达的那个方向]的最短次数
struct node
{
int x,y;//当前点的坐标
int t;//1=>N 2=>S 3=>W 4=>E 方向编号
int time;//从起点到当前点的最短时间
};
queue<node> q;//队列q
char ch;//读入起点的方向
int cto;//起点的方向
void change()
{
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
if(sd[i][j]==1)//如果当前格为障碍物,则它的四个顶点都不能走
{
a[i-1][j]=1;
a[i][j-1]=1;
a[i-1][j-1]=1;
a[i][j]=1;
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
scanf("%d",&sd[i][j]);
}
}
cin>>x11>>y11>>x2>>y2>>ch;
switch(ch)
{
case 'N': cto=1;break;
case 'S': cto=2;break;
case 'W': cto=3;break;
case 'E': cto=4;break;
}//判断ch代表的方向
change();//把方格地图转化为机器人可以走的格点地图
node first;//起点
// first.x=x11;
// first.y=y11;
// first.t=cto;
// first.time=0;
// q.push(first);//起点入队 ,这样也行
q.push((node) { //但是这里得用结构体名字
x11,y11,cto,0
});
node u,d;
while(!q.empty())
{
u=q.front();
q.pop();
for(int i=1;i<=4;++i)
{
int zhuan=abc[i];//[顺时针转i下的那个方向]的最短旋转次数
//求出旋转完了以后方向的编号fangx(为了方便讨论,全部当做顺时针旋转)
int fangx=fft[u.t]+i;//此时fangx为下标
if(fangx>=5) fangx-=4;
fangx=ft[fangx];//此时fangx为方向编号
//此时fangx存的是由当前点顺时针转了i次后到达的方向的编号
for(int j=1;j<=3;++j)//走1~3步
{
int lsx=u.x+fx[fangx]*j;//计算按当前旋转方向走j步的坐标
int lsy=u.y+fy[fangx]*j;
if(lsx>=n || lsx<=0 || lsy>=m || lsy<=0 || (lsx==x11&&lsy==y11) || a[lsx][lsy]==1)
{
//判断边界和障碍物 (特判:是否为起点)
break;
}
if((u.time+zhuan+1<f[u.x+fx[fangx]*j][u.y+fy[fangx]*j] || f[u.x+fx[fangx]*j][u.y+fy[fangx]*j]==0) && a[u.x+fx[fangx]*j][u.y+fy[fangx]*j]==0)
{//如果当前点可以刷新距离,就入队
d.x=u.x+fx[fangx]*j;
d.y=u.y+fy[fangx]*j;
d.t=fangx;
d.time=u.time+zhuan+1;
f[u.x+fx[fangx]*j][u.y+fy[fangx]*j]=d.time;
q.push(d);
}
}
}
}
if(f[x2][y2]==0 && (x2!=x11 || y2!=y11))//如果为0,代表不能走到
{
cout<<"-1";
}
else//否则输出终点的距离
cout<<f[x2][y2];
return 0;
}
小结:兽人永不为奴!