#C08L10P01. C08.L10.宽度优先搜索BFS.概述
C08.L10.宽度优先搜索BFS.概述
一、简介
广度优先搜索算法(Breadth-First Search,BFS)是一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
二、例子
假设你居住在旧金山,要从双子峰前往金门大桥。你想乘公交车前往,并希望换乘最少。可乘坐的公交车如下。
要找出换乘最少的乘车路线,你将使用什么样的算法?
一步就能到达金门大桥吗?下面突出了所有一步就能到达的地方。
金门大桥未突出,因此一步无法到达那里。两步能吗?
金门大桥也未突出,因此两步也到不了。三步呢?
金门大桥突出了!因此从双子峰出发,可沿下面的路线三步到达金门大桥。
还有其他前往金门大桥的路线,但它们更远(需要四步)。这个算法发现,前往金门大桥的最短路径需要三步。这种问题被称为最短路径问题(shorterst-path problem)。你经常要找出最短路径,这可能是前往朋友家的最短路径,也可能是国际象棋中把对方将死的最少步数。解决最短路径问题的算法被称为广度优先搜索。要确定如何从双子峰前往金门大桥,需要两个步骤。
(1) 使用图来建立问题模型。
(2) 使用广度优先搜索解决问题。
三、广度优先搜索算法
假设你经营着一个芒果农场,需要寻找芒果销售商,以便将芒果卖给他。在Facebook,你与芒果销售商有联系吗?为此,你可在朋友中查找。
这种查找很简单。首先,创建一个朋友名单。
然后,依次检查名单中的每个人,看看他是否是芒果销售商。
假设你没有朋友是芒果销售商,那么你就必须在朋友的朋友中查找。
检查名单中的每个人时,你都将其朋友加入名单。
这样一来,你不仅在朋友中查找,还在朋友的朋友中查找。别忘了,你的目标是在你的人际关系网中找到一位芒果销售商。因此,如果Alice不是芒果销售商,就将其朋友也加入到名单中。这意味着你将在她的朋友、朋友的朋友等中查找。使用这种算法将搜遍你的整个人际关系网,直到找到芒果销售商。这就是广度优先搜索算法。
四、查找最短路径
再说一次,广度优先搜索可回答两类问题。
第一类问题:从节点A出发,有前往节点B的路径吗?(在你的人际关系网中,有芒果销售商吗?)
第二类问题:从节点A出发,前往节点B的哪条路径最短?(哪个芒果销售商与你的关系最近?)
刚才你看到了如何回答第一类问题,下面来尝试回答第二类问题——谁是关系最近的芒果销售商。例如,朋友是一度关系,朋友的朋友是二度关系。
在你看来,一度关系胜过二度关系,二度关系胜过三度关系,以此类推。因此,你应先在一度关系中搜索,确定其中没有芒果销售商后,才在二度关系中搜索。广度优先搜索就是这样做的!在广度优先搜索的执行过程中,搜索范围从起点开始逐渐向外延伸,即先检查一度关系,再检查二度关系。顺便问一句:将先检查Claire还是Anuj呢?Claire是一度关系,而Anuj是二度关系,因此将先检查Claire,后检查Anuj。
你也可以这样看,一度关系在二度关系之前加入查找名单。
你按顺序依次检查名单中的每个人,看看他是否是芒果销售商。这将先在一度关系中查找,再在二度关系中查找,因此找到的是关系最近的芒果销售商。广度优先搜索不仅查找从A到B的路径,而且找到的是最短的路径。
注意,只有按添加顺序查找时,才能实现这样的目的。换句话说,如果Claire先于Anuj加入名单,就需要先检查Claire,再检查Anuj。如果Claire和Anuj都是芒果销售商,而你先检查Anuj再检查Claire,结果将如何呢?找到的芒果销售商并非是与你关系最近的,因为Anuj是你朋友的朋友,而Claire是你的朋友。因此,你需要按添加顺序进行检查。有一个可实现这种目的的数据结构,那就是队列(queue)。
五、队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
其中,C++提供了队列(queue)的容器可以直接使用:
#include<queue> //头文件
queue<int> a; //定义了一个整型队列a
a.empty(); //判断队列是否为空
a.size(); //返回队列的长度
a.pop(); //删除队列第一个元素
a.push(5); //把5放进队列当中
a.front(); //获取队列第一个元素的值
a.back(); //获取队列最后一个元素的值
六、广度优先搜索算法实现
我们要从“你”出发找到“ANUJ”,关系表示为下图,使用广度优先搜索算法。
先概述一下这种算法的工作原理。
但这样可能会出现一些问题,Peggy既是Alice的朋友又是Bob的朋友,因此她将被加入队列两次:一次是在添加Alice的朋友时,另一次是在添加Bob的朋友时。因此,搜索队列将包含两个Peggy。
但你只需检查Peggy一次,看她是不是芒果销售商。如果你检查两次,就做了无用功。因此,检查完一个人后,应将其标记为已检查,且不再检查他。
如果不这样做,就可能会导致无限循环。假设你的人际关系网类似于下面这样。
一开始,搜索队列包含你的所有邻居。
现在你检查Peggy。她不是芒果销售商,因此你将其所有邻居都加入搜索队列。
接下来,你检查自己。你不是芒果销售商,因此你将你的所有邻居都加入搜索队列。
以此类推。这将形成无限循环,因为搜索队列将在包含你和包含Peggy之间反复切换。
检查一个人之前,要确认之前没检查过他,这很重要。为此,你可使用一个列表来记录检查过的人。
#include<iostream>
#include<fstream>
#include<algorithm>
#include<queue>
using namespace std;
queue<int>a,b,c;
int aa[220];
int ans=-1,n,m;
int x,y,d[220],q,p;
void bfs(int x)
{
d[x]=1;
a.push(x);
c.push(1);
int wx,wy,wz;
while(!a.empty())
{
wx=a.front();
a.pop();
wz=c.front();
c.pop();
int nx= wx + aa[wx];
if(nx==p)
{
ans=wz;
return ;
}
if(nx>=1&&nx<=n&&d[nx]==0)
a.push(wz+1);
nx=wx-aa[wx];
if(nx==p)
{
ans=wz;
return ;
}
if(nx>=1&&nx<=n&&d[nx]==0)
{
a.push(nx);
d[nx]=1;
c.push(wz+1);
}
}
}
int main()
{
cin>>n>>q>>p;
if(q==p)
{
cout<<0;
return 0;
}
for(int i=1;i<=n;i++)
cin>>aa[i];
bfs(q);
cout<<ans;
return 0;
}
相关
在以下作业中: