这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战。
图的搜索
两种常见的图搜索算法分别是深度优先搜索(depth-first search,DFS)和广度优先搜索(breadth-first search,BFS)。
在深度优先搜索中,我们以根节点(或者任意节点)为起始点,完整地搜索一个分支后,再搜索另一个分支,也就是说,我们先向深度方向搜索(因此命名为深度优先搜索),再向广度方向搜索。
在广度优先搜索中,我们以根节点(或者任意节点)为起始点,先搜索其相邻节点再搜索相邻节点的子节点,也就是说,我们先向广度方向搜索(因此命名为广度优先搜索),再向深度方向搜索。
请参见下图关于图的深度优先搜索与广度优先搜索的描述(假设相邻节点按照数字顺序进行迭代)。
值得注意的是,BFS和DFS通常用于不同的场景。如要访问图中所有节点,或者访问最少的节点直至找到想找的节点,DFS一般最为简单。
但是,如果我们想找到两个节点中的最短路径(或任意路径),BFS一般说来更加适宜。想象如下场景:将整个世界的朋友关系用图表示,并找出Ash和Vanessa之间的一条路径。
在深度优先搜索中,可以选择如下路径:Ash -> Brian -> Carleton -> Davis -> Eric -> Farah -> Gayle -> Harry -> Isabella -> John -> Kari...此路径与所求路径相差甚远。我们可能搜索了世界上大部分的朋友关系,但是都没有意识到,Vanessa实际上是Ash的朋友。我们最终会找到该路径,但是或许会耗时许久。此方法也无法找出最短路径。
在广度优先搜索中,可以尽可能地离Ash近一些。我们或许需要迭代很多Ash的朋友,但是除非必须,我们不会搜索距离Ash更远的朋友。如果Vanessa是Ash的朋友,或者是他朋友的朋友,我们会相对快速地发现这个事实。
深度优先搜索
在DFS中,我们会先访问节点a,然后遍历访问a的每个相邻节点。在访问a的相邻节点b时,我们会在继续访问a的其他相邻节点之前先访问b的所有相邻节点,也就是说,在继续搜索a的其他子节点之前,我们会先穷尽搜索b的子节点。
注意,前序和树遍历的其他形式都是一种DFS。主要区别在于,对图实现该算法时,我们必须先检查该节点是否已访问。如果不这么做,就可能陷入无限循环。
下面是实现DFS的伪代码。
广度优先搜索
BFS相对不太直观,除非之前熟悉其实现方式,否则大部分求职者在实现该方法时会觉得无从下手。他们面临的主要障碍在于(错误地)认为BFS是通过递归实现的。其实不然,它是通过队列实现的。
在BFS中,我们会在搜索a的相邻节点之前先访问节点a的所有相邻节点。你可以将其想象为从a开始按层搜索。用到队列的迭代法往往最为有效。
当面试官要求你实现BFS时,关键在于谨记队列的使用。用了队列,这个算法的其余部分自然也就成型了。