循环的深入-宽度优先搜索

摘要:此算法用于图论中寻找最短路径dij或者mst最小生成树,是入门搜索算法之一,另一个是dfs深度优先搜索。此算法利用遍历可能的节点储存于队列中,再从队列取出节点,利用循环尝试搜索下一个可用节点,直至找到目标点为止。

 

关键字:宽度优先搜索;循环;遍历;节点。

 

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

已知图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的后裔。

广度优先搜索遍历类似于树的按层次遍历。

对于无向连通图,广度优先搜索是从图的某个顶点v0出发,在访问v0之后,依次搜索访问v0的各个未被访问过的邻接点w1,w2,…。然后顺序搜索访问w1的各未被访问过的邻接点,w2的各未被访问过的邻接点,…。即从v0开始,由近至远,按层次依次访问与v0有路径相通且路径长度分别为1,2,…的顶点,直至连通图中所有顶点都被访问一次。

广度优先搜索的顺序不是唯一的。

具体描述如下:

设图G的初态是所有顶点均未访问,在G 中任选一顶点i作为初始点,则广度优先搜索的基本思想是:

(1)从图中的某个顶点V出发,访问之;并将其访问标志置为已被访问,即visited[i]=1;

(2)依次访问顶点V的各个未被访问过的邻接 点,将V的全部邻接点都访问到;

(3)分别从这些邻接点出发,依次访问它们的未被访问过的邻接点,并使“先被访问的顶 点的邻接点”先于“后被访问的顶点的邻接点”被访问,直到图中所有已被访问过的顶 点的邻接点都被访问到。

依此类推,直到图中所有顶点都被访问完为止 。

广度优先搜索在搜索访问一层时,需要记住已被访问的顶点,以便在访问下层顶点时,从已被访问的顶点出发搜索访问其邻接点。所以在广度优先搜索中需要设置一个队列Queue,使已被访问的顶点顺序由队尾进入队列。在搜索访问下层顶点时,先从队首取出一个已被访问的上层顶点,再从该顶点出发搜索访问它的各个邻接点。

接下来用关键代码来一步一步的来解释:

 

首先可以用结构体来存储节点信息,这样方便以后调用该节点的信息。结构体里面是表示横坐标的x,和表示纵坐标的y,第三个是表示到此节点时所用的步数。

接下来看关键算法部分,这里用到了queue队列,语法很简单可以自行百度。首先保证队列为空,如果不为空,则采用while循环进行删除。接着,将起点放入队列,作为第一个节点。下面再写一个while循环,当队列不为空,即符合条件的节点还未走完。声明一个节点,用来存储队列中最靠前的节点。然后判断当前节点是否是目的点,如果是目的点那么直接return到达目的点时的步数。如果并未到达目的点,那么我们就可以声明一个二维数组,用来标记,如果遍历到就在相应的位置标记,表示此点我们已经遍历过了。此代码中用visit二维数组作为标记数组,如果标记为1则表示已经遍历过。不过我们可以优化内存复杂度,在原来储存的图上标记。接下来我们以这个节点为基础,根据走法步骤使用for循环寻找下一个符合条件的节点。此代码中使用的方法比较好,先声明一个二维数组,然后根据条件储存要到达下一个节点需要进行的所有操作。比如是走迷宫,那么我们到达下一个节点可以左走一步,或者右走一步,或者上走一步,或者下走一步。那么我们在走法二维数组里面可以存在4x2的数组里面,4代表4种不同的走法,2代表一种走法中x和y的变化。那么就是{0,1},{0,-1},{1,0},{-1,0}。for循环里面用i代表不同的走法,0代表x的走法,1代表y的走法。于是next.x=x+stepArr[i][0],同理next.y=y+stepArr[i][1]。好了这下我们可以走到下一步了,此时还要需要判断走到此点是否合法,于是我们应该判断改点是否已经超出边界,是否是可以走的地方,还有一点特别需要注意就是,该点在过去是否已经遍历过,定义的visit数组就派上用场了,过去我们将遍历过的点在visit数组中表示为1,那么当他不为1时就可以走。需要注意的就是visit数组的初始化,一般处理是应用memset将visit数组初始化为0,在全局变量中,数组会自动初始化为0,但为了避免未知错误,我们还是将其初始化一次。回到此题,当该点可以走且过去没有走过(visit数组上该值为0),那么我们到达该点时步骤会多+1,并且将该点储存在队列中,这里可以思考到,for循环里面,该点到达下一点的距离都一样,那么这几个点都是平行的,queue队列里面储存的节点都是有先后顺序的(queue的性质),于是我们储存的步数都是最先到达该点的步数(如果后面有到达该点的,那么因为visit已经填了1所以不会改变该点的step值)。所以当我们到达目的点的时候step是最少的,所以bfs可以用来寻找从起点到达目的点的最短路径(或者时间)。

Bfs当然也可以用来计算最小生成树,鉴于篇幅太长,和许多概念问题在这不在讨论,有兴趣可以去搜索prim最小生成树(mst)。Bfs可以想象为扩散式搜索,就像石头扔在平静的湖上,产生向外扩张的波纹,每个波纹都代表同一个时间所到达的地方。另一种方式dfs可以想象为一条路走到底,如果未到达终点遇到“死路”了,就往回走,直到回溯到另一个有分叉路的节点。两种方式各有好处,bfs在大多数情况下先找到终点,dfs可以找出所有可能的路径(方便标记走过的路径)。

你可能感兴趣的:(搜索)