在我们日常刷题的过程中,我们会通过遍历来获取自己想要的数组,搜索算法就是典型的一种。
而搜索算法中也分深搜(DFS),广搜(BFS),而今天,我们来说说广搜。
什么是广搜
广搜全称叫广度优先搜索,或叫宽度优先搜索,简称BFS。人如其名,他不像深搜一路干到底,而是一层一层的搜索,如图
当然,广搜有很多种,这只是广搜和二叉树的结合,不过大体就是像这样,一层一层的搜索
广搜原理及其伪代码
如上图,是广搜全过程(无重复搜索)
我来给大家解读下
刚开始时是1号位,由于广搜是一层层的搜索,所以搜完2号位后不能着急搜2号位的其他点,而是要把1号位能搜完的全部搜完,于是我们搜3号位。这时1号位搜完了,所以我们可以搜其他位了,我们可以先搜3号位,同搜1号位,4号位和5号位搜完了…直到最后61号位和60号位搜完了,到62号位了,最后结束。
【分析】我们口述时是直接更换搜索位置的,这对于我们人来说很轻松,但是大家想过没有,这用c++该怎么实现呢?其实很简单,用数组模拟就行,用一个变量记录现在搜的是谁,如果搜完了,就跳到下一位。
代码如下(注:此代码只是表达思路,并不是伪代码):
int ab[1050];
int head=0;
if(搜完了){
head++;
}
else{for(){继续搜索 加入数据}
}
不过虽然数组模拟通俗易懂,但对于某些情很极端,如你要加入1千万的数据,数组就存不下了,对于这种数组存不下的情况该怎么办呢?这时,我们得先冷静下来,思考,为什么数组存不下,就是因为删除的部分没有删除数据,只要把前面的数删了,问题就解决了,哎!恰好c++的STL里面的队列可以解决此问题,而且和数组差不多,甚至会比数组模拟更好用
好,看代码(注:此代码只是表达思路,并不是伪代码)
queue<int>q;
初始化
如果队列不为空{int f=q.front();q.pop();//弹出if(满足答案){return ...; } 搜索{ if(满足条件){q.push(...); } }
}
好的我们终于分析完了,总结:广搜一般用队列,如果数据量较小,就用数组模拟(尽量用队列),
如果队列不为空(就是没搜完)就继续搜,搜完一个弹出队列,如果满足答案就返回(BFS函数大多是用返回值int),否则慢慢搜答案。对于搜到的答案如果满足条件就入队
好的伪代码如下
int bfs(变量){queue<类型>q;初始化while(!q.empty()){ //队列不为空 类型 f=q.front(),q.pop();if(判断能否返回){return ; //返回 } for(枚举(搜索)){判断{初始化搜索结果 q.push(...); //加入 如果能返回就直接返回 } }} return -1; //返回不了
}
广搜经典例题
知道广搜原理后就可以上手了!
1330:【例8.3】最少步数
【题目描述】
在各种棋中,棋子的走法总是一定的,如中国象棋中马走“日”。有一位小学生就想如果马能有两种走法将增加其趣味性,因此,他规定马既能按“日”走,也能如象一样走“田”字。他的同桌平时喜欢下围棋,知道这件事后觉得很有趣,就想试一试,在一个(100×100)的围棋盘上任选两点A、B,A点放上黑子,B点放上白子,代表两匹马。棋子可以按“日”字走,也可以按“田”字走,俩人一个走黑马,一个走白马。谁用最少的步数走到左上角坐标为(1,1)的点时,谁获胜。现在他请你帮忙,给你A、B两点的坐标,想知道两个位置到(1,1)点可能的最少步数。
【输入】
A、B两点的坐标。
【输出】
最少步数。
【输入样例】
12 16
18 10
【输出样例】
8
9
【分析】很明显是道广搜题(因为我们需要多重考虑),重!!!:我们在搜索时一定要方向数组,而且也要标记数组,毕竟会走重复路就会超时(亲测)。这时我们也要用到结构体了,因为考虑到了步数,位置,我们需要结构体里的变量标记,不然得至少开三维数组
#include<bits/stdc++.h>
using namespace std;
int xy[][2]={{-2,-1},{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{2,2},{2,-2},{-2,-2},{-2,2}};
//方向数组与标记数组
int flag[105][105];
//结构体
struct node{int x,y;int step;//步数
};
int bfs(int x,int y){queue<node>q;//初始化node cs; flag[x][y] = 1;cs.step = 0 , cs.x = x , cs.y = y;q.push(cs);while(!q.empty()){node f = q.front(); q.pop(); if(f.x == 1 && f.y == 1){return f.step;}for(int i = 0;i < 12;i++){//搜索int xx = f.x + xy[i][0];int yy = f.y + xy[i][1];//数据判断if(xx >= 1 && xx <= 100 && yy >= 1 && yy <= 100 && flag[xx][yy] == 0){node nw;flag[xx][yy] = 1;nw.x = xx , nw.y = yy , nw.step = f.step + 1;if(xx == 1 && yy == 1){return nw.step;}q.push(nw);}}}
}
int main()
{
int x,y;
cin >> x >> y;
cout << bfs(x , y) << endl;
memset(flag,0,sizeof(flag)); //调用俩次,flag清0
cin >> x >> y;
cout << bfs(x , y) << endl;return 0;
}
1254:走出迷宫
【题目描述】
当你站在一个迷宫里的时候,往往会被错综复杂的道路弄得失去方向感,如果你能得到迷宫地图,事情就会变得非常简单。
假设你已经得到了一个n×m
的迷宫的图纸,请你找出从起点到出口的最短路。
【输入】
第一行是两个整数n
和m
(1≤n,m≤100
),表示迷宫的行数和列数。
接下来n
行,每行一个长为m
的字符串,表示整个迷宫的布局。字符‘.’表示空地,‘#’表示墙,‘S’表示起点,‘T’表示出口。
【输出】
输出从起点到出口最少需要走的步数。
【输入样例】
3 3
S#T
.#.
…
【输出样例】
6
【分析】经典广搜,方向数组,标记数组,大力枚举,结构体一条龙
#include<bits/stdc++.h>
using namespace std;
int n,m;
int flag[105][105]; //标记数组
char mp[105][105];
int dx[] = {0 , 0 , 1 , -1}; //方向数组
int dy[] = {1 , -1 , 0 , 0};
struct node{ //结构体int x,y;int step;
};
int bfs(int lx,int ly,int zx,int zy){queue<node>q;//初始化node g; flag[lx][ly] = 1;g.x = lx,g.y = ly,g.step = 0;q.push(g);while(!q.empty()){node f = q.front();q.pop();if(f.x == zx && f.y == zy){ //答案判断return f.step;}//大力出奇迹for(int i = 0;i < 4;i++){int fx = f.x + dx[i];int fy = f.y + dy[i];//范围判断if(fx >= 1 && fx <= n && fy >= 1 && fy <= m && flag[fx][fy] == 0 && mp[fx][fy] != '#'){flag[fx][fy] = 1;node now;now.x = fx , now.y = fy , now.step = f.step + 1;q.push(now);if(fx == zx && fy == zy){ //答案判断return f.step + 1;}}}}return -1;
}
int main()
{
cin >> n >> m;
int sx,sy,zx,zy;
for(int i = 1 ;i <= n;i++){for(int j = 1;j <= m;j++){cin>>mp[i][j];if(mp[i][j] == 'S'){ //拿下起点位置sx=i;sy=j;}if(mp[i][j] == 'T'){ //拿下终点位置zx=i;zy=j;}}
}
cout << bfs(sx,sy,zx,zy);return 0;
}
数字转换
给你两个数s,t, 从s开始,每次从小于当前数的质因子中挑选一个数加给当前数,问最少加几次能到达t
输入
第一行输入一个整数T,表示测试组数
接下来T行每行两个整数s,t
输出
对于每组测试数据输出一个最小步数,如果无法到达,输出-1
具体格式见样例输出
样例
输入 1
2
6 12
6 13
输出 1
Case 1: 2
Case 2: -1
提示
约定:
T<=500,1<=s<=100,1<=t<=1000
【分析】这里已经不是搜索题了,但是仍要用广搜的思想(一层层枚举),不过值得注意的是,这里需要用素数筛法+广搜
#include<bits/stdc++.h>
using namespace std;
int s,t;
const int N=1010;
int flag[N];
void init(){ //素数筛flag[1]=1;for(int i=2;i<N;i++){if(flag[i]==0){for(int j=i+i;j<N;j+=i){flag[j]=1;}}}
}
int vis[N],dis[N];//vis标记数组,dis答案数组
int bfs(int s,int t){queue<int>q;q.push(s);vis[s]=1;dis[s]=0;while(!q.empty()){int f=q.front();q.pop();if(f==t){return dis[t];}for(int i=2;i*i<=f;i++){if(f%i==0&&flag[i]==0){int x=i;if(f+x<=t&&vis[f+x]==0){vis[f+x]=1;q.push(f+x);dis[f+x]=dis[f]+1;}}if(f%i==0&&f/i!=i&&flag[f/i]==0){int x=f/i;if(f+x<=t&&vis[f+x]==0){vis[f+x]=1;q.push(f+x);dis[f+x]=dis[f]+1;}}}}return -1;
}
int main()
{
init();
int T,ca=1;
cin>>T;
while(T--){
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis)); //归0
cin>>s>>t;
cout<<"Case "<<ca<<": "<<bfs(s,t)<<"\n";
ca++;
}return 0;
}
营救计划
易错题警告!!!
在一个n∗m的迷宫里,有一个萌新被困住了,你作为一个久经码场的战士,决定去营救萌新。地图中还会有一些守卫,你必须打败守卫才能通过守卫所在的位置,打败守卫需要花费1分钟,移动一步需要花费一分钟,每次你只能往上下左右某个方向移动一步。问你最少需要花费多少时间才能救到萌新。
输入
第一行输入两个整数 n,m
接下来n行每行输入m个字符
'#'代表障碍物,无法直接通过
'.'代表空地,可以直接通过
'G’代表守卫,你需要打败他才能通过
'M’代表萌新所在的位置
'@'表示你所在的位置
输出
如果可以营救到萌新,就输出最少需要花费的分钟数
如果不可以,输出“You can’t save Mengxin”
样例
输入 1
7 8
#.#####.
#.M#…@.
#…#G…
…#…#.#
#…##…
.#…
…
输出 1
13
输入 2
2 2
M.
G@
输出 2
2
提示
1<=n,m<=200
【分析】经典广搜,分析什么?于是,一顿小连招,你会,发现错了!为什么错了呢,这是经典广搜,不过是他却是考到了广搜->状态,我们只需分类讨论就行了
#include<bits/stdc++.h>
using namespace std;
const int N=222;
int n,m;
int dis[N][N][2]; //状态数组
int vis[N][N][2];
char mp[N][N];
int dx[]={1,0,-1,0};
int dy[]={0,1,0,-1};
struct node{ int x,y,flag;
};
int bfs(int sx,int sy){queue<node>q; node t;t.x=sx;t.y=sy;t.flag=0;q.push(t);vis[sx][sy][1]=0;dis[sx][sy][1]=0;while(!q.empty()){node f=q.front();q.pop();if(mp[f.x][f.y]=='G'&&f.flag==0&&vis[f.x][f.y][1]==0){//特判dis[f.x][f.y][1]=dis[f.x][f.y][0]+1;vis[f.x][f.y][1]=1;f.flag=1;q.push(f);continue;}for(int i=0;i<4;i++){node g;int nowx=f.x+dx[i];int nowy=f.y+dy[i];g.x=nowx;g.y=nowy;if(nowx<0||nowx>=n||nowy<0||nowy>=m||mp[nowx][nowy]=='#'){continue;}if(mp[nowx][nowy]=='G'){//状态情况考虑if(vis[nowx][nowy][0]==0){vis[nowx][nowy][0]=1;dis[nowx][nowy][0]=dis[f.x][f.y][f.flag]+1;g.flag=0;q.push(g);}}else if(mp[nowx][nowy]=='M'){return dis[f.x][f.y][f.flag]+1;}else{if(vis[nowx][nowy][1]==0){vis[nowx][nowy][1]=1;dis[nowx][nowy][1]=dis[f.x][f.y][f.flag]+1;g.flag=1;q.push(g);}}}}return -1;
}
int main()
{cin>>n>>m;
int sx,sy;
for(int i=0;i<n;i++){
cin>>mp[i];for(int j=0;j<m;j++){ if(mp[i][j]=='@'){sx=i;sy=j;}}
}
int ret=bfs(sx,sy);
if(ret==-1){cout<<"You can't save Mengxin";
}
else{cout<<ret;
}return 0;
}
完结!!!撒花!!!