欢迎光临散文网 会员登陆 & 注册

每天一个数据结构知识——图的广度优先遍历BFS

2023-08-29 22:35 作者:一天三只皮卡丘  | 我要投稿

        图的广度优先遍历和树的广度优先遍历类似,树是层序遍历,从根节点出发找到其所有的孩子结点。而图的广度优先就是从一个结点开始,搜索所有的相邻节点。而图与树不同点在于,树不存在回路,所以搜索相邻的结点不会搜到已经访问过的结点,而图搜索相邻顶点时可能会重复访问。所以,图需要对每个顶点进行标记。

        那么具体如何实现对图的广度优先遍历呢?之前了解到,树的层序遍历需要一个辅助队列,BFS也是采取类似的方法。这里我们主要用到两个图的基本操作,一个是寻找第一个相邻节点FirstNeighbor,一个是寻找相邻节点的下一个相邻节点NextNeighbor。同时定义一个bool型的数组visited[]标记每个顶点有没有被访问过。

算法如下:

void BFS(G,v){   //从顶点v出发遍历图G

    visit(v);

    visited(v)=true;

    Enqueue(Q,v);        //v进入队列Q

    while(isempty(Q)){ //队列非空

        Dequeue(Q,v);  //队头出队

        for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor){ //遍历与队头顶点相邻的所有顶点

            if(!visited(w)){ //邻接节点没有被访问过

                visit(w);

                visited(w)=true;

                Enqueue(Q,w); //访问并入队

                                }

                    }

        }

}

        总体来看,广度优先遍历和层序遍历从实现方式来看十分类似(因为树其实就是一个特殊的图),只是要记住广度优先遍历每次访问完需要标记,访问时也要排除掉标记的。但是图的存储方式不同遍历的次序也是不同的,邻接表的遍历次序是唯一的。

        并且此方法对于非连接图的遍历也存在弊端,不过只需在遍历停止后,再遍历一遍visited[]数组,如果存在数组值为false,在以该节点为起始节点进行BFS遍历,直到没有没访问的节点即可。实现方法如下:

void BFSTraverse(G){

for(i=0;i<G.vexnum;i++)visited[i]=false; //初始化visited数组

InitQueue(Q);

for(i=0;i<G.vexnum;i++){

        if(visited[i]==false){ 

            BFS(G,i);

                }

}

}

        显然对于无向图,调用BFS的次数=连通分量数。BFS的空间复杂度,辅助队列最大为O(|v|)。此算法的主要开销来自于访问顶点和找到相邻的邻接点,对于邻接矩阵,寻找顶点邻接点的时间复杂度为O(v),共有v个顶点因而查找所有顶点的邻接点的时间复杂度为O(v^2)。对于邻接表,查找每个顶点的邻接点只需O(E)的时间复杂度,总时间复杂度为O(V+E)。

        因为我们在进行广度优先时避免了重复访问,因此有的边不需要经过就能访问到所有的点,那么我们把这些边去掉就得到了广度优先生成树。而由于邻接表不唯一可能遍历的序列不唯一,从而导致广度优先生成树也不唯一。由此,我们可以想到,对于非连通图的BFS可以得到广度优先森林。

        在了解了广度优先搜索的原理后,下面我们开始学习其最重要的一个应用——求最短路径。最短路径问题有几个常见的场景:单源最短路径,每对顶点间的最短路径。BFS可以用来求无权图的单元最短路径。

        我们只需在BFS算法的基础上增加两个数组,一个数组存放源节点到其他节点的距离,一个数组存放该路径的上一个顶点。实现方法如下:

void BFS_Min_Distance(G,u){

visited(u)=true;

for(i=0;i<G.vexnum;i++){d[i]=-1;

path[i]=-1;

}//初始化路径长度和路径前驱节点

d[u]=0;

EnQueue(Q,u);

while(isEmpty(Q)){

DeQueue(Q,u);//队头元素出队

for(w=FirstNeigobor(G,u);w>=0;w=NextNeighbor(G,u,w)){//访问邻接节点

while(!visited[w]){

d[w]=d[u]+1;

path[w]=u;

visited[w]=true;

EnQueue(Q,w);}

        }//if

    }//while

}

通过回溯path数组,我们可以找到最短路径,多么精妙的算法。同时我们可以发现,通过广度优先生成树的节点和根节点的距离就是最短路径。

每天一个数据结构知识——图的广度优先遍历BFS的评论 (共 条)

分享到微博请遵守国家法律