广度优先搜索(BFS)
自己对于广度优先搜索的理解:
与深度优先搜索(DFS)相对应,广度优先搜索是指,立足于当前的某一个节点,每一次将遍历所有离该节点最近的节点(也就是距离为1的节点),然后再将这些刚刚被当作最近节点遍历的节点全部依次作为当前节点进行下一次对所有最近节点的遍历,依次类推,直到找到想要的节点和路径。
从我自己的算法描述可以看出,广度优先搜索是一种顾名思义的算法,类似于层序遍历,即每一次的搜索都将达到最广的范围,也就是每次都将触手可及的所有节点都找到,就像展开一颗一层一层排列的树一样。
为了更加形象地表示广度优先搜索的过程,我们可以从上面这张图来阐述一次广度优先搜索的过程。
加入我们将v3节点作为搜索的起始节点s,那么从s出发,我们第一次搜索先遍历那些距离s为1的所有节点,我们可以找到这样的节点有v1,v6。本轮搜索结束,然后我们再依次将刚才找到的v1,v6节点为当前节点,寻找距离当前节点为1的节点,所以我们又找到了v2,v4.这里需要注意的是虽然有节点和v6节点只一边之遥,但是根据图的有向性,从v6节点并不能到达这些节点,所以我们并没有从v6节点找到别的节点。又一次,我们从v2,v4节点找到了v5,v7节点,这里v3已经被找到过了,不应当走遍历重复节点,所以到此所有可遍历的节点都被遍历了,广度优先搜索结束。
广度优先搜索的代码实现
我们可以利用一个队列来实现上面所述的算法,先将当前节点入队,然后再遍历每一个当前节点将可以从当前节点达到的距离为1的所有节点入队,然后将当前节点出队,依次类推,这样利用队列将当前节点从队首出队,再将相邻节点从队尾入队的方式可以保证所有下一节点都在所有当前节点全部被处理完成后再进行处理。
#include <stdio.h>
#include <stdlib.h>
#define INIT_SIZE 50
//利用二维数组定义邻接表
static map[8][8]={
0,0,0,0,0,0,0,0,
0,0,1,0,1,0,0,0,
0,0,0,0,1,1,0,0,
0,1,0,0,0,0,1,0,
0,0,0,1,0,1,1,1,
0,0,0,0,0,0,0,1,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,1,0
} ;//map[i][j]=1表示可以从节点i到达节点j
//定义一个循环队列以实现算法
typedef struct Queue
{
int front;
int rear;
int elements[INIT_SIZE];
}Queue;
void Init_Queue(Queue *q)
{
q->front=q->rear=0;
}
void InQueue(int element,Queue *q) //入队
{
q->elements[q->rear++]=element;
q->rear=(q->rear)%INIT_SIZE;
}
int DeQueue(Queue *q)//出队
{
int element=q->elements[q->front++];
q->front=(q->front)%INIT_SIZE;
return element;
}
int Empty(Queue *q)
{
if(q->rear==q->front)
{
return 1;
}
else return 0;
}
int BFS(int start,int end)//BFS算法从start位置到end位置寻找最短路径
{
int last=start,flag=0;
Queue q;
int i,j,t,step=0;
Init_Queue(&q);
InQueue(start,&q);
while(!Empty(&q))
{
t=DeQueue(&q);
if(t==last)
{
flag=1;
step++;
}
for(j=1;j<=7;j++)
{
if(map[t][j]==1&&j!=start)
{
if(j==end) return ++step;
if(flag==1) last=j;
InQueue(j,&q);
}
}
flag=0;
}
return step;
}
main()
{
int step,start=3,end=7;
step=BFS(start,end);
printf("从%d节点到%d节点的最短距离为:%d\n",start,end,step);
}
对上面的代码,进行一些补充解释:
算法思路上面已经大致说明,主要就是通过队列出队入队的逻辑实现类似层序遍历的操作。
而为了记录遍历的层数,也就是当前节点到起始节点s间的距离,我们这里使用了两个变量来实现来实现了这一功能:
其中last变量表示记录当前层的最后一个节点,其实第一层只有s节点一个,所以我们将last默认赋值为起始节点s,然后在每当last节点要遍历临近节点时,我们需要在这个过程中不断更新last的值,因为只有last节点遍历的最后一个节点才是下一层的最后一个节点。
那么具体到算法实现上的操作就是判断如果出队的节点是上一次被标记的last节点,那么意味这这一层的最后一个节点已经被处理完了,那么就给步数step加1,然后通过一个标志flag来打开一个开关以更新last的值,因为在last节点被出队时,意味着下一层的节点也即将被查找完毕,此时我们需要不断更新last节点查找到的值来作为下一层的新last节点。
:
t=DeQueue(&q);
if(t==last)//判断是否到达last节点
{
flag=1;//通过flag打开更新last节点的开关
step++;//本层处理完毕,步数加1
}
for(j=1;j<=7;j++)
{
if(map[t][j]==1&&j!=start)
{
if(j==end) return ++step;
if(flag==1) last=j;//如果开关被打开,那么查找的同时更新last的值
InQueue(j,&q);
}
}
这样我们就可以随时记录遍历的层数或者是走的步数了。
路径的记录
上面的代码虽然可以确保遍历所有节点和路径,并找到最少的步数,但是仍然无法获取最短路径的记录,即便我们确实在不经意间遍历了最短路径,但是我们仍需要开辟额外的空间取记录最短路径。
要确保路径是最短的,那么就要确保这条路径上的每一个节点的上一个节点在所有可以达到本节点的节点中具有最浅的深度,相对于起始节点而言。所以为了记录最短的路径,我们不但需要开辟一个记录最短路径的空间,还需要开辟一个记录节点深度的空间。
为此,我们再专门定义两个数组,我将它们分别称为depath和minpath,其中depath数组的每一个元素对应每一个节点的深度,也就是从s节点出发到达这一节点的最小距离,而minpath用来记录可以到达该节点的所有节点中深度最小的一个。
例如v1和v2都可以到达v4,但是v1的深度为1,v2的深度为2,所以我们记录可以到达v4的最浅临近节点minpath[4]=1.
我们在全局声明这样的两个数组,然后对BFS算法做以下更改:
int depath[8]={-1,-1,-1,-1,-1,-1,-1,-1} ;
int minpath[8]={-1,-1,-1,-1,-1,-1,-1,-1} ;
更改后的BFS算法:
int BFS(int start,int end)//BFS算法从start位置到end位置寻找最短路径
{
int last=start,flag=0;
Queue q;
int i,j,t,step=0;
Init_Queue(&q);
InQueue(start,&q);
while(!Empty(&q))
{
t=DeQueue(&q);
if(depath[t]==-1)//如果某一节点的depath值为1,说明该节点还没有被赋予深度
{
depath[t]=step;//那么将当前搜索的层数赋值给该节点的深度值
}
if(t==last)
{
flag=1;
step++;
}
for(j=1;j<=7;j++)
{
if(map[t][j]==1&&j!=start)
{
if(flag==1) last=j;
if(j==end)
{
minpath[j]=minpath[j]==-1?(t):(depath[minpath[j]]>depath[t]?t:minpath[j]);
return ++step;
}
InQueue(j,&q);
//更新可以到达该处的最近节点
minpath[j]=minpath[j]==-1?(t):(depath[minpath[j]]>depath[t]?t:minpath[j]);
}
}
flag=0;
}
return step;
}
关键的更改代码在于:
if(depath[t]==-1)//如果某一节点的depath值为1,说明该节点还没有被赋予深度
{
depath[t]=step;//那么将当前搜索的层数赋值给该节点的深度值
}
minpath[j]=minpath[j]==-1?(t):(depath[minpath[j]]>depath[t]?t:minpath[j]);
第一处代码意在给第一次被探索到的新节点赋于其最小深度,因为第一次被探索时该节点可以得到的深度一定是最小的。
第二处的代码用来获取或更新可以到达节点j的最小深度的临近节点。
如果minpath[j]的值为-1,说明还没有临近节点探索到它,那么就将t作为这样的第一个节点。如果值不等于1,那么说明已经有minpath[j]这样一个节点发现了它,所以我们将t的深度和minpath[j]节点的深度进行比较,将minpath[j]更新为它们当中深度最小的那一个。
如此以来,当广度优先搜索完成后,我们将得到每一个节点的深度和可到达它们的最浅临近节点,而后者的意义在于,我们可以通过一直追溯这样的最浅节点来得到一条最小的路径。其获得路径的简单代码如下:
i=end;
while(minpath[i]!=start)
{
printf("%d->",minpath[i]);
i=minpath[i];
}
printf("%d",start);
这样可以通过遍历minpath数组获得一条从终点到起点的最短路径,而我们只要通过一个栈的逻辑就可以将它转换为从起点到终点的一条最小路径,这样的实现是非常简单的。
以最开始我们给出的那个有向图为例,我们通过给粗的BFS得出如下结果: