BFS(宽度优先搜索)
Breadth First Seach
和深度优先搜索不同,宽度优先搜索是层层递进,像是地毯式搜索,找到答案就停止(从根开始往下一层一层的搜),这时候选择用队列来维护当前层,
如果队列不空说明当前层还没往下搜,讲队头取出,目的是将当前层取出,如果还有下一层就把下一层的节点加入队列以此往复直到找到答案为止。
(记录层的状态,如果已经遍历过了就更新状态就不会被重复经过)
下面是一个使用BFS的经典例子
AcWing 844. 走迷宫
给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1表示不可通过的墙壁。
最初,有一个人位于左上角 (1,1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。
请问,该人从左上角移动至右下角 (n,m)处,至少需要移动多少次。
数据保证 (1,1)处和 (n,m) 处的数字为 0,且一定至少存在一条通路。
输入格式
第一行包含两个整数 n和 m。
接下来 n行,每行包含 m 个整数(0 或 1),表示完整的二维数组迷宫。
输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数。
数据范围
1≤n,m≤100
输入样例:
5 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
输出样例:
8
本题问的是从左上角走到右下角的最小格数
由于上下左右都是迈出一个,他们的权重都是1
所以这一题问最短路就可以用BFS来求解
层数就相当于步数
在起点的时候相当于第0层
然后往各个方向试探
这时候我们可以引入一个方向的偏移量
dx和dy 方便我们模拟坐标系点的移动
0 1 F G H
A 1 E 1 G
B C D E F
C 1 1 1 G
D E F 1 H
我们按照无差别地毯搜索的视角来看待对这个迷宫的搜索
发现到右下角的距离是 H 即答案是8
(这里方便区分01和其他数字 所以我就干脆把步数的数字换成字母ABC了)
下面请看ac代码
#include <iostream>
#include <algorithm>//pair
#include <string.h>//memset
using namespace std;
const int N=110;
typedef pair<int,int> PII;
/*
-----------------
兔吃窝边草
一层一层吃
吃到饱为止
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
0 1 ⑥ ⑦ ⑧
① 1 ⑤ 1 ⑦
② ③ ④ ⑤ ⑥
③ 1 1 1 ⑦
④ ⑤ ⑥ 1 ⑧
-----------------
*/
int n,m;//全局变量 方便bfs函数使用
int g[N][N],d[N][N];//g数组用来存地图,d数组用来记录步数状态
PII q[N*N];//队列 存点 注意开一个N*N 防止队列点太多
int hh,tt=-1;//队头队尾
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
//由于这里写的上下左右 并且下面搜索循环有顺序
//所以会出现顺序问题 后面会重点提醒
//↓行是x →列是y
int bfs()
{
q[++tt]={0,0};//讲第一个点放入队列
memset(d,-1,sizeof d);//如果没走过 那么他的状态就是-1
d[0][0]=0;//将第0层的距离设置为0
while(hh<=tt)//尝试扩展下一层
{
auto t=q[hh++];
for(int i=0;i<4;i++)//如果有多个方向可以走就说明这一层有多个点
{
int x=t.first+dx[i],y=t.second+dy[i];
if(x>=0&&x<n&&y>=0&&y<m&&!g[x][y]&&d[x][y]==-1)//如果尝试的点没遍历过
{
d[x][y]=d[t.first][t.second]+1;//记录那个点是第几层扩展到的
q[++tt]={x,y};//将这一个点放入队列中
}
}
}
return d[n-1][m-1];
}
int main(void)
{
cin>>n>>m;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
cin>>g[i][j];//输入地图
cout<<bfs();
return 0;
}
通过队列 我们将当前层弹出 然后试图存入下一层
是当前层都搜索完了才会搜索下一层 这个顺序也是控制好了
对于这题中有一个问题
不知道能不能发散思维
就是往下一层扩展的时候,方向顺序是由我们自己决定
举个例子
我们是按上下左右的顺序
为了更好观察我所说的情况
我将搜索的路径打印出来
用一个Prev[N]来记录该点的前一个经过的点是谁
如果我们从终点找prev找到起点
输出的路径是反的 为了好看 我用栈来暂时存放点
然后就实现反过来输出
stk[N],ta;
在遍历下一个点时
int x=0,y=4;//我们对于图中红绿色这两条路径最后的那个点思考
while(x||y)//没走到起点之外
{
auto t=Prev[x][y];//取出Prev
stk[++ta]=t;//栈存入Prev
x=t.first,y=t.second;//点更新成Prev
}
while(ta)//出栈
{
cout<<"("<<stk[ta].first<<','<<stk[ta].second<<")"<<endl;
ta--;
}
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
的输出结果①:
走的是这条
因为上在dx里靠前 所以队列先存到的是红色这条路径 (可以自己模拟一下)
最后0,4这个点是红色路径到达的
如果我们将右放在下相对前面
int dx[4]={0,0,-1,1},dy[4]={1,-1,0,0};
输出结果②是:
所以这是有顺序问题的
我对于BFS的理解目前到这里
BFS最短一定是先到的
以后有更深的了解还会来打个标记 A.A
acwing 847. 图中点的层次
给定一个 n个点 m条边的有向图,图中可能存在重边和自环。
所有边的长度都是 1,点的编号为 1∼n。
请你求出 1号点到 n 号点的最短距离,如果从 1 号点无法走到 n 号点,输出 −1。
输入格式
第一行包含两个整数 n和 m。
接下来 m行,每行包含两个整数 a 和 b,表示存在一条从 a 走到 b 的长度为 1的边。
输出格式
输出一个整数,表示 1号点到 n号点的最短距离。
数据范围
1≤n,m≤105
输入样例:
4 5
1 2
2 3
3 4
1 3
1 4
输出样例:
1
因为是无权的
求最短距离就可以灵活运用bfs直接利用走迷宫的思路
即 "先被扫到"的就是最短的
#include <iostream>
#include <string.h>
using namespace std;
const int N=100010;
int h[N],e[N],ne[N],idx;//idx控制从1-n
int d[N],n,m;//d记录状态 没遍历过就是-1 第一次遍历才是最短的 n,m全局变量函数有用
int q[N],hh,tt=-1;//队列维护BFS
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}//邻接表存图
int bfs()
{
q[++tt]=1;//起点入队
memset(d,-1,sizeof d);//d初始化
d[1]=0;//1到1的距离是0
while(hh<=tt)//BFS板子
{
int t=q[hh++];//取出队头
for(int i=h[t];i!=-1;i=ne[i])//从邻接表找这个点直接到达的下一个点
{
int j=e[i];//取出那个点
if(d[j]==-1)//如果没遍历过 st数组的变形
{
d[j]=d[t]+1;//记录距离
q[++tt]=j;//入队
}
}
}
return d[n];//答案
}
int main(void)
{
memset(h,-1,sizeof h);//拉链初始化
cin>>n>>m;
for(int i=0;i<m;i++)//m条边
{
int a,b;
cin>>a>>b;
add(a,b);//存有向边
}
cout<<bfs();
return 0;
}