前几天学习了广度优先搜索(Breadth-First Search),也就是BFS
啥是BFS呢,从名字不难看出来,这是一种搜索算法,并且以广度优先,很类似于现在的导航技术,能够找到从出发点到终点的最短走法。如果你你还是不懂,可以先看一下下面一道题目:
抓住那头牛问题
农夫丢失了他的一头牛,他决定追回他的牛。已知农夫和牛在一条直线上,初始位置分别为𝑥(𝑥>0)和𝑦,假定牛在原地不动。 农夫每分钟的行走方式为:
ㅤ
前进一步 𝑜𝑟 后退一步 𝑜𝑟 走到𝟐𝒙的位置
ㅤ
计算他最少需要几分钟追上他的牛。 (你能打印出行走的路线吗?)
让我们一起来分析一下这道题目:
如果你一开始直接思考整个过程,可能有点晕
所以我们弄个简单的,就画个一维的坐标图,点用来表示位置
假设一开始农夫在位置3,牛🐂在位置5
试着想一想我们要如何解决这种情况
如果要符合条件,也就是人的位置和牛🐂的位置相同
而人的位置改变只有三种方法
前进一步 ㅤ或者 ㅤ后退一步 ㅤ或者ㅤ 走到𝟐𝒙的位置
对于图中的农夫👨🌾来说,他从位置3走一步可以到达
位置4ㅤ位置2ㅤ位置6
那我们是不是只要跟随农夫👨🌾的路线走,到达这三个位置之后又可以有三种选择,再次到达新的位置
又因为要求最少需要多少步,那就是农夫👨🌾第一次到达牛🐂的位置的时候就是花费最少步数的情况
因此,我们可以画出一张图来帮助我们理解(画的一般,凑合着看吧)
农夫👨🌾一开始在位置3然后走一步可以到达位置2 位置4 位置6
到达位置2 位置4 位置6之后又可以到达新的位置
注意:这里我们寻找最少时间花费,也就是说不要走重复的路,比如说
位置3 ->位置2->位置3
ㅤ
ㅤ
Step1:用数组记录到过的位置
因此我们还需要加入一个vis数组来记录某个点是否已经走过
然后我们想,这个操作,一开始是位置3,然后分出到3个位置
我们从位置3到达位置位置4
那我们是因该先处理前面从位置3到达位置2和位置6的情况还是直接处理到达位置4后的情况
仔细想一下,我们前面想到的方法是一步一步走,每次只走一步,判断是否人的位置和牛🐂的位置相等,如果相等了,我们就可以得到一个计数变量,也就是我们要的最少需要几分钟
因此我们确定要先处理从位置3到达位置2和位置6的情况
不难想到我们可以用数据结构中的队列来模拟这个进程
ㅤ
ㅤ
Step2:用队列模拟这个过程
一开始把位置3加入队列中,每次操作取出队列元素,判断是否到达牛🐂的位置,如果是就返回计数变量,如果不是的话,就加入三个位置:4 2 6分别代表农夫👨🌾走一步能到达的你地方,不断重复这个动作直到农夫👨🌾到达牛🐂的位置
也就是下面这个动画
解释:图中黑色的圆是一开始的位置,蓝色的圆代表是第一步可以到达的位置,红色的圆是第二步可以到达的位置
注意一下,每次到达新的位置的时候要先判断这个位置之前是否已经走过!
最后输出的结果就是一个计数变量,记录第几步
最好能够自己动手写出这个过程
ㅤ
ㅤ
Step3:用数组记录并打印出行走路径
不难想到,我们可以用一个数组来记录行动路径 实际上是记录上一个位置所在的地方
根据刚刚的图我们可以画出下面的表格
数组下表表示一个位置,其数值表示上一个位置的数组下标,让我们找一找,数组下标为5其数值是4,接着找数组下标是4的数值是3,也就是到达另外一开始农夫的位置👨🌾,然后在反向遍历出来就可以输出行走路径
ㅤ
附上完整代码
#include<bits/stdc++.h>
using namespace std;
#define Show_Queue 1 /*两个宏定义用来表示是否输出队列以及路径数组*/
//#define Show_Wayarray 1
#define NOFOUND -1
#define MAXNUMBER 100
bool vis[MAXNUMBER] = {false}; /*用来记录是否曾经走过*/
int dis[MAXNUMBER] = {0}; /*用来记录从开始行动的这里的最短移动次数*/
int action[MAXNUMBER] = {0}; /*用来记录行动路径 实际上是记录前驱节点*/
int cnt = 0; /*用来数数 队列操作次数*/
/*用来判断数据是否有效*/
int judge(int x)
{
if(x > 0 && x <= MAXNUMBER && vis[x] == false)
return true;
return 0;
}
int bfs(int x,int y)
{
int next;
queue<int>que;
que.push(x);
vis[x] = true;
while(!que.empty())
{
cnt++;
printf("%03d:",cnt);
#ifdef Show_Queue
/*遍历整个队列*/
int queue_size = que.size();
for(int i = 0; i < queue_size; i++)
{ //queue_size 必须是固定值
cout << que.front() << " ";
que.push(que.front());
que.pop();
}
cout << endl;
#endif
/*取出队首元素*/
int u = que.front();
que.pop();
/*找到目标*/
if(u == y)
return dis[u];
next = u + 1;
/*判断下一步是否有效*/
if( judge(next) )
{
que.push(next);
vis[next] = true;
dis[next] = dis[u] + 1;
action[next] = u;
}
next = u - 1;
if( judge(next) )
{
que.push(next);
vis[next] = true;
dis[next] = dis[u] + 1;
action[next] = u;
}
next = 2 * u;
if( judge(next) )
{
que.push(next);
vis[next] = true;
dis[next] = dis[u] + 1;
action[next] = u;
}
}
return NOFOUND;
}
/*用递归来打印出路径*/
void Find_Way(int start, int end)
{
if(start == end)
return ;
Find_Way(start, action[end]);
/*step是计算出来两个位置的差,来判断农夫的移动方式*/
int step = end - action[end];
if(step == 1)
printf("从 %3d 向前走1步到%3d\n",action[end], end);
else if(step == -1)
printf("从 %3d 向后走1步到%3d\n",action[end], end);
else
printf("从 %3d 向前2倍步到%3d\n",action[end], end);
}
int main()
{
int i;
int start = 3;
int end = 5;
int ans;
ans = bfs(start, end);
printf("最快要走 %d 步到达!\n",ans);
Find_Way(start, end);
#ifdef Show_Wayarray
for(int temp = 0; temp < MAXNUMBER; ++temp)
{
printf("%d-%d\n",temp, action[temp]);
}
#endif
return 0;
}
跑一下程序:
001:3
002:4 2 6
003:2 6 5 8
004:6 5 8 1
005:5 8 1 7 12
最快要走 2 步到达!
从 3 向前走1步到 4
从 4 向前走1步到 5
得到答案
ㅤ
ㅤ
ㅤ
总结
总结一下bfs的做法:
1. 将起点star加入队列𝑞中,同时对该点进行更新
2. while 队列𝑞非空:
ㅤㅤ2.1 取出队首元素𝑢
ㅤㅤ2.2 判断𝑢是否为终点,若是终点跳出循环,并输出答案
ㅤㅤ2.3 遍历𝑢可到达的合法位置𝑣,将𝑣加入队列,并更新
ㅤ
作者是初学的小白,写这篇文章来来记录一下自己的学习过程,可能文章有一些错误,欢迎指正
ㅤ
附上基础2的链接:[算法]BFS广度优先搜索(基础2)
您的点赞和收藏是我前进的动力