广度优先搜索(BFS)的原理和应用
二叉树中的层序遍历就属于一种BFS(Board First Search)
层序遍历会得到ABCDEFG的层序优先序列(BFS序列)。在层序遍历过程中,可以注意到先访问的节点的孩子节点必然先被访问(如访问了A后访问B和C,那么B的孩子结点一定在C的孩子结点前被访问――仅针对下一层的孩子而言)。据这个特性,可以用队列来实现这个遍历。
void Layer(bitree *p)
{
queue<node*> Q; //定义一个队列
node *N;
Q.push(*p); //起始点入队
while(!Q.empty())
{
N=Q.front();
Q.pop();
cout<<N->data<<endl;
if(N->lchild!=NULL) //将扩展子结点(仅一层)全都入队
Q.push(N->lchild);
if(N->rchild!=NULL)
Q.push(N->rchild);
}
}
BFS比它更进一步,可以针对图的结构进行BFS遍历
的BFS(从V1开始)路径是
广搜的一般结构如下:
定义一个队列;
起始点入队;
while(队列不空){
队头结点出队;
若它是所求的目标状态,跳出循环;
否则,将它扩展出的子结点,全都入队;
}
若循环中找到目标,输出结果;
否则输出无解;
它的主要特点是:
n 每次队头元素出队时,扩展其全部的子结点,并用队列记录下来。
n 搜索过程没有回溯,是一种牺牲空间换取时间的方法。
来看个BFS的经典例子吧Knight Moves zoj(1091)!
题目:国际象棋棋盘上有个马,要跳到指定坐标,求最少的跳的步数!
输入:
a1 h8
输出:
To get from a1 to h8 takes 6 knight moves.
先来看看跳马的规则,马只能往自己在的坐标的2*3或3*2范围内跳动!
例如:要从a1到e4:
第1次所有能跳到的点
第2次所有能跳到的点
第3次所有能跳到的点
当第3次时达到了e4,说明已经达到目的。
以下是我完全可运行的源程序:
#include<iostream>
#include<memory>
#include<queue>
using namespace std;
int dir[8][2]=
{{2,1},{2,-1},{-2,1},{-2,-1},{1,2},{-1,2},{-1,-2},{1,-2}
}; //八个可能方向
int flag[8][8]; //标记棋盘中一个点是否已经被踏过
struct node
{
int x,y,step; //棋盘点的坐标和几次可达的信息
};
int main()
{
char a,b,c,d;
node N,P;
queue<node> Q;
memset(flag,0,64);
cin>>a>>b>>c>>d;
int r1=a-'a';
int c1=b-'1';
int r2=c-'a';
int c2=d-'1';
N.x=r1;N.y=c1;N.step=0;
Q.push(N); //初始节点入队
flag[r1][c1]=1;
while(!Q.empty())
{
N=Q.front();Q.pop(); //出队
if(N.x==r2&&N.y==c2) //达到目的,则跳出
break;
for(int i=0;i<8;i++) //八方向搜索
{
int tx=N.x+dir[i][0];
int ty=N.y+dir[i][1];
if(tx>=0&&tx<8&&ty>=0&&ty<8&&flag[tx][ty]==0) //看是否在棋盘内和是否已经被踏过
{
P.x=tx;P.y=ty;P.step=N.step+1;
Q.push(P);
flag[tx][ty]=1; //标记为被踏过
}
}
}
cout<<"To get from "<<a<<b<<" to "<<c<<d<<' ';
cout<<"takes "<<N.step<<" knight moves."<<endl;
}
宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。
已知图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的后裔。
下面的宽度优先搜索过程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相邻的节点集合。
procedure BFS(G,S);
begin
1. for 每个节点u∈V[G]-{s} do
begin
2. color[u]←White;
3. d[u]←∞;
4. π[u]←NIL;
end;
5. color[s]←Gray;
6. d[s]←0;
7. π[s]←NIL;
8. Q←{s}
9. while Q≠φ do
begin
10. u←head[Q];
11. for 每个节点v∈Adj[u] do
12. if color[v]=White then
begin
13. color[v]←Gray;
14. d[v]←d[v]+1;
15. π[v]←u;
16. Enqueue(Q,v);
end;
17. Dequeue(Q);
18. color[u]←Black;
end;
end;