当前位置首页 > 百科> 正文

宽度优先搜寻

2019-09-12 09:16:02 百科
宽度优先搜寻(广度优先算法)

宽度优先搜寻

广度优先算法一般指本词条

宽度优先搜寻算法(又称广度优先搜寻)是最简便的图的搜寻算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都採用了和宽度优先搜寻类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜寻整张图,直到找到结果为止。

基本介绍

  • 中文名:宽度优先搜寻算法
  • 外文名:BFS
  • 别称:广度优先搜寻
  • 套用学科:计算机
  • 适用领域範围:计算机
  • 适用领域範围:计算机算法

概述

BFS,其英文全称是Breadth First Search。 BFS并不使用经验法则算法。从算法的观点,所有因为展开节点而得到的子节点都会被加进一个先进先出的伫列中。一般的实验里,其邻居节点尚未被检验过的节点会被放置在一个被称为 open 的容器中(例如伫列或是鍊表),而被检验过的节点则被放置在被称为 closed 的容器中。(open-closed表)
自然界中的宽搜自然界中的宽搜

详细解释

已知图G=(V,E)和一个源顶点s,宽度优先搜寻以一种系统的方式探寻G的边,从而“发现”s所能到达的所有顶点,并计算s到所有这些顶点的距离(最少边数),该算法同时能生成一棵根为s且包括所有可达顶点的宽度优先树。对从s可达的任意顶点v,宽度优先树中从s到v的路径对应于图G中从s到v的最短路径,即包含最小边数的路径。该算法对有向图和无向图同样适用。
之所以称之为宽度优先算法,是因为算法自始至终一直通过已找到和未找到顶点之间的边界向外扩展,就是说,算法首先搜寻和s距离为k的所有顶点,然后再去搜寻和S距离为k+l的其他顶点。
为了保持搜寻的轨迹,宽度优先搜寻为每个顶点着色:白色、灰色或黑色。算法开始前所有顶点都是白色,随着搜寻的进行,各顶点会逐渐变成灰色,然后成为黑色。在搜寻中第一次碰到一顶点时,我们说该顶点被发现,此时该顶点变为非白色顶点。因此,灰色和黑色顶点都已被发现,但是,宽度优先搜寻算法对它们加以区分以保证搜寻以宽度优先的方式执行。若(u,v)∈E且顶点u为黑色,那幺顶点v要幺是灰色,要幺是黑色,就是说,所有和黑色顶点邻接的顶点都已被发现。灰色顶点可以与一些白色顶点相邻接,它们代表着已找到和未找到顶点之间的边界。
在宽度优先搜寻过程中建立了一棵宽度优先树,起始时只包含根节点,即源顶点s.在扫描已发现顶点u的邻接表的过程中每发现一个白色顶点v,该顶点v及边(u,v)就被添加到树中。在宽度优先树中,我们称结点u 是结点v的先辈或父母结点。因为一个结点至多只能被发现一次,因此它最多只能有--个父母结点。相对根结点来说祖先和后裔关係的定义和通常一样:如果u处于树中从根s到结点v的路径中,那幺u称为v的祖先,v是u的后裔。

与深度优先搜寻的对比

深度优先搜寻用栈(stack)来实现,整个过程可以想像成一个倒立的树形:
1、把根节点压入栈中。
2、每次从栈中弹出一个元素,搜寻所有在它下一级的元素,把这些元素压入栈中。并把这个元素记为它下一级元素的前驱。
3、找到所要找的元素时结束程式。
4、如果遍历整个树还没有找到,结束程式。
广度优先搜寻使用伫列(queue)来实现,整个过程也可以看做一个倒立的树形:
1、把根节点放到伫列的末尾。
2、每次从伫列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到伫列的末尾。并把这个元素记为它下一级元素的前驱。
3、找到所要找的元素时结束程式。
4、如果遍历整个树还没有找到,结束程式。

伪代码实现

下面的宽度优先搜寻过程BFS假定输入图G=(V,E)採用邻接表表示,对于图中的每个顶点还採用了几种附加的数据结构,对每个顶点u∈V,其色彩存储于变数color[u]中,结点u的父母存于变数π[u]中。如果u没有父母(例如u=s或u还没有被检索到),则 π[u]=NIL,由算法算出的源点s和顶点u之间的距离存于变数d[u]中,算法中使用了一个先进先出伫列Q来存放灰色节点集合。其中head[Q]表示伫列Q的队头元素,Enqueue(Q,v)表示将元素v入队, Dequeue(Q)表示对头元素出队;Adj[u]表示图中和u相邻的节点集合。
BFS(G,S)
foreachu∈V[G]-{s}
do
color[u]←White;
d[u]←∞;
π[u]←NIL;
end;
color[s]←Gray;
d[s]←0;
π[s]←NIL;
Q←{s}
while(Q≠φ)
do
u←head[Q];
for each v∈Adj[u]
do
if(color[v]=White)
then
color[v]←Gray;
d[v]←d[u]+1;
π[v]←u;
Enqueue(Q,v);
end;
Dequeue(Q);
color[u]←Black;
end;
end;
end;
图1展示了用BFS在例图上的搜寻过程。黑色边是由BFS产生的树枝。每个节点u内的值为d[u],图中所示的伫列Q是第9-18行while循环中每次叠代起始时的伫列。伫列中每个结点下面是该结点与源结点的距离。
图1 BFS在一个无向图上的执行过程
过程BFS按如下方式执行,第1-4行置每个结点为白色,置d[u]为无穷大,每个结点的父母置为NIL,第5行置源结点S为灰色,即意味着过程开始时源结点已被发现。第6行初始化d[s]为0,第7行置源结点的父母结点为NIL,第8行初始化伫列0,使其仅含源结点s,以后Q伫列中仅包含灰色结点的集合。
程式的主循环在9-18行中,只要伫列Q中还有灰色结点,即那些已被发现但还没有完全搜寻其邻接表的结点,循环将一直进行下去。第10行确定伫列头的灰色结点为u。第11-16行的循环考察u的邻接表中的每一个顶点v。如果v是白色结点,那幺该结点还没有被发现过,算法通过执行第13-16行发现该结点。首先它被置为灰色,距离d[v]置为d[u]+1,而后u被记为该节点的父母,最后它被放在伫列Q的队尾。当结点u的邻接表中的所有结点都被检索后,第17 -18行使u弹出伫列并置成黑色。

实际套用

BFS在求解最短路径或者最短步数上有很多的套用。
套用最多的是在走迷宫上。
单独写代码有点泛化,取来自九度1335闯迷宫一例说明,并给出C++/Java的具体实现。
在一个n*n的矩阵里走,从原点(0,0)开始走到终点(n-1,n-1),只能上下左右4个方向走,只能在给定的矩阵里走,求最短步数。n*n是01矩阵,0代表该格子没有障碍,为1表示有障碍物。
int mazeArr[maxn][maxn]; //表示的是01矩阵
int stepArr[4][2] = {{-1,0},{1,0},{0,-1},{0,1}}; //表示上下左右4个方向
int visit[maxn][maxn]; //表示该点是否被访问过,防止回溯,回溯很耗时。
核心代码。基本上所有的BFS问题都可以使用类似的代码来解决。

C++

structNode{intx;inty;intstep;Node(intx1,inty1,intstep1):x(x1),y(y1),step(step1){}};intBFS(){Nodenode(0,0,0);queue<Node>q;while(!q.empty())q.pop();q.push(node);while(!q.empty()){node=q.front();q.pop();if(node.x==n-1&&node.y==n-1){returnnode.step;}visit[node.x][node.y]=1;for(inti=0;i<4;i++){intx=node.x+stepArr[i][0];inty=node.y+stepArr[i][1];if(x>=0&&y>=0&&x<n&&y<n&&visit[x][y]==0&&mazeArr[x][y]==0){visit[x][y]=1;Nodenext(x,y,node.step+1);q.push(next);}}}return-1;}

Java

privatestaticintbfs(){Nodenode=newNode(0,0,0);Queue<Node>queue=newLinkedList<Node>();queue.add(node);while(!queue.isEmpty()){NodenewNode=queue.poll();visit[newNode.x][newNode.y]=1;for(inti=0;i<4;i++){intx=newNode.x+stepArr[i][0];inty=newNode.y+stepArr[i][1];if(x==n-1&&y==n-1){returnnewNode.step+1;}if(x>=0&&y>=0&&x<n&&y<n&&visit[x][y]==0&&mazeArr[x][y]==0){Nodenext=newNode(x,y,newNode.step+1);queue.add(next);}}}return-1;}privatestaticclassNode{privateintx;privateinty;privateintstep;publicNode(intx,inty,intstep){super();this.x=x;this.y=y;this.step=step;}}

最佳化

广度搜寻的判断重複如果直接判断十分耗时,我们一般藉助哈希表来最佳化时间複杂度。

总结

在证明宽度优先搜寻的各种性质之前,我们先做一些相对简单的工作 ——分析算法在图G=(V,E)之上的运行时间。在初始化后,再没有任何结点又被置为白色。因此第12行的测试保证每个结点至多只能进入伫列一次,因而至多只能弹出伫列一次。入队和出队操作需要O(1)的时间,因此伫列操作所占用的全部时间为O(V),因为只有当每个顶点将被弹出伫列时才会查找其邻接表,因此每个顶点的邻接表至多被扫描一次。因为所有邻接表的长度和为Q(E),所以扫描所有邻接表所花费时间至多为O(E)。初始化操作的开销为O(V),因此过程BFS的全部运行时间为O(V+E),由此可见,宽度优先搜寻的运行时间是图的邻接表大小的一个线性函式。
声明:此文信息来源于网络,登载此文只为提供信息参考,并不用于任何商业目的。如有侵权,请及时联系我们:baisebaisebaise@yeah.net