双向BFS算法思想
理解
双向BFS适用于已经直到了起点和终点的状态下使用,从起点和终点两个方向开始进行搜索,可以极大地提高单向BFS的搜索效率。
可以设置两个队列,一个队列保存从起点开始搜索的状态,另一个队列用来保存从终点开始搜索的状态,当两个队列都非空时才能继续进行循环。循环内不断对元素较少的队列进行BFS操作(以免退化为单向BFS),每次操作从队列弹出一个元素,与该元素相连的后续未访问元素压入队列中。如果某一个状态出现相交的情况,那么就出现了答案。
当两种颜色相遇的时候,说明两个方向的搜索树遇到一起,这个时候就搜到了答案。
针对扩展方式还可以进行优化——交替逐层扩展。由于两端扩展节点个数可能差别很大,所以每次选择节点个数少的队列进行扩展。return的条件仍为有节点重叠。
正确性证明如下(证明不会因为提前break而错过最优解):
假设当前得到最优解为X,不失一般性,假设X现在是从起点搜索到的,则X已经被终点搜索过了,所以才终止搜索。使用反证法证明,假设存在最优解Y。
-
若X和Y在同一层,则当且仅当从终点搜索时Y位于X前面层,Y才为最优解。设从起点搜索Y的前一个节点为z,由于终点方向从Y所在层,扩展到X所在层,这个过程是连续,所以在这个过程中会首先扩展到z,则z被当做最优解,程序return。如下图所示:
-
Y位于X的后面,令X和Y相隔i层,则Y为最优解的充分条件为:从终点方向,Y与X的层数之差大于i,记为j。令起点方向上,z是y的前j个节点。那么从终点方向搜索时,z应该不晚于x被搜到。因此也不会错过最优解。如下图所示:
所以使用交替逐层能保证算法正确性。
我们知道,BFS是往广处去搜索,我们把这想象成一个以起点为圆心的一个圆,每向前走一步,就是圆的半径增大一个单位,而圆的每个单位面积上是一种状态,当圆增大到目标点那么大时,找到了答案,搜索结束。那么显然的,当半径较大时,每走一步就会有十分大量的状态需要来储存( S = π r 2 S=\pi r^2 S=πr2)。通常的,我们用“判重”的方法来解决,但是在这里,还有一种优化方法:双向BFS:
双向搜索适用于起点和终点状态都很明确的搜索题。试想,我从起点推算到终点,与我从终点推算到起点,最后得到的最少步骤数一定是相等的,所以我们可以从起点和终点同时搜索。同样用圆来打比方,假设以起点为圆心画圆时,其半径为 r r r,以终点为圆心画圆时,其半径为 R R R,那么显然有: π r 2 + π R 2 ≤ π ( r + R ) 2 \pi r^2+\pi R^2\leq \pi (r+R)^2 πr2+πR2≤π(r+R)2。
蓝色圆是从起点出发搜索得到的圆,绿色圆是 从终点出发搜索得到的圆,起点和终点同时搜索,双向BFS,此时只需要搜索的状态空间是 π r 2 + π R 2 \pi r^2+\pi R^2 πr2+πR2,而如果我们只是用单向的BFS算法,那么得到的状态空间是 π ( r + R ) 2 \pi (r+R)^2 π(r+R)2,显然 π r 2 + π R 2 ≤ π r 2 + π R 2 + 2 π r R \pi r^2+\pi R^2\leq \pi r^2+\pi R^2+2\pi rR πr2+πR2≤πr2+πR2+2πrR。由此可知,双向BFS效率更高。
双向BFS的正确做法是交替逐层搜索,而不是交替节点搜索,为什么交替节点搜索是错误的呢?
求S-T的最短路,交替节点搜索(一次正向节点,一次反向节点)时:
- Step 1 : S –> 1 , 2
- Step 2 : T –> 3 , 4
- Step 3 : 1 –> 5
- Step 4 : 3 –> 5 返回最短路为4,错误的,事实是3,S-2-4-T
正确做法的是交替逐层搜索,保证了不会先遇到非最优解就跳出,而是检查完该层所有节点,得到最优值。也即如果该层搜索遇到了对方已经访问过的,那么已经搜索过的层数就是答案了,可以跳出了,以后不会更优的了。当某一边队列空时就无解了。
为什么要优先选择队列元素少的进行BFS呢?
因为长度小的队列扩展出的元素数量少,效率高。提供速度的关键在于使状态扩展得少一些,所以优先选择队列长度较少的去扩展,保持两边队列长度平衡。这比较适合于两边的扩展情况不同时,一边扩展得快,一边扩展得慢。如果两边扩展情况一样时,加了后效果不大,不过加了也没事。
况不同时,一边扩展得快,一边扩展得慢。如果两边扩展情况一样时,加了后效果不大,不过加了也没事。