0x26
广搜变形
1.双端队列BFS
在最基本的广度优先搜索中,每次沿着分支的扩展都记为“一步”,我们通过逐层搜索,解决了求从起始状态到每个状态的最少步数的问题。这其实等价于在一张边权均为1的图上执行广度优先遍历,求出每个点相对于起点的最短距离(层次)。在第0x21
节中我们曾讨论过这个问题,并得到了“队列中的状态的层数满足两段性和单调性”的结论。从而我们可以知道,每个状态在第一次被访问并入队时,计算出的步数即为所求。
然而,如果图上的边权不全为1呢?换句话说,如果每次扩展都有不同的“代价”,我们想求出起始状态到每个状态的最小代价,应该怎么办呢?我们不妨先来讨论图上的边权要么是1、要么是0的情况。
在这样的图上,我们可以通过双端队列广搜来计算。算法框架与一般的广搜类似,只是在每个节点上沿着分支拓展时稍作改变。如果这条分支是边权为0的边,就把沿该分支到达的新节点从队头入队;如果这条分支是边权为1的边,就像一半的广搜一样从队尾入队。这样一来,我们就仍然能保证,任意时刻广搜队列中的节点对应的距离值都具有“两段性”和“单调性”。
2.优先队列BFS
对于更加具有普适性的情况,也就是每次扩展都有各自不同的“代价”时,求出起始状态到每个状态的最小代价,就相当于在一张带权图求出从起点到每个节点的最短路。此时,我们有两个解决方案:
方法一
仍然使用一般的广搜,采用一般的队列。
这时我们不能保证每个状态第一次入队时就能得到最小代价,所以只能允许一个状态被多次更新、多次进出队列。我们不断执行搜索,直到队列为空。
整个广搜算法对搜索树进行了重复遍历与更新,直到“收敛”到最优解,其实也就是“迭代”的思想。最坏情况下,该算法的时间复杂度会从一般广搜的
O
(
N
)
O(N)
O(N)增长到
O
(
N
2
)
O(N^2)
O(N2)。对应在最短路问题中,就是我们将在0x61
节介绍的
S
P
F
A
SPFA
SPFA算法。
方法二
改用优先队列进行广搜。
这里的“优先队列”就相当于一个二叉堆。我们每次可以从队列中取出当前代价最小的状态进行扩展(该状态一定是最优的,因为队列中其他状态的当前代价都不小于它,所以以后就更不可能在更新它了),沿着各条分支把到达的新状态加入优先队列。不断执行搜索,直到队列为空。
在优先队列BFS
中,每个状态也会被多次更新、多次进出队列,一个状态也可能以不同的代价在队列中同时存在。不过当每个状态第一次从队列中被取出时,就得到了从起始状态到该状态的最小代价。之后若再被取出,则可以直接忽略,不进行拓展。所以,优先队列BFS
中的每个状态只拓展一次,时间复杂度只多了维护二叉堆的代价。若一般广搜的复杂度为
O
(
N
)
O(N)
O(N),则优先队列BFS
的复杂度为
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN)。对应在最短路问题中,就是我们将在0x61
节中介绍的堆优化
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra算法。
下图就展示了优先队列BFS
的详细过程,每条边的数值标明了每个状态向下扩展所需的代价,节点中的数值标明了每个状态上求出的“当前代价”,灰色节点标明了每个时刻在堆中出现至少一次的节点。
建议不熟悉最短路相关算法的读者,简要对0x61
节的两个单源最短路径算法进行阅读,这对于理解并写出正确的优先队列BFS
有很大的帮助。
至此,我们就可以对BFS
的形式,按照对应在图上的边权情况进行分类总结:
1.问题只计最少步数,等价于在边权都为1的图上求最短路。
使用普通的
BFS
,时间复杂度 O ( N ) O(N) O(N)。每个状态只访问(入队)一次,第一次入队时即为该状态的最少步数。
2.问题每次扩展的代价可能是0或1,等价于在边权只有0和1的图上求最短路。
使用双端队列
BFS
,时间复杂度 O ( N ) O(N) O(N)。每个状态被更新(入队)多次,只扩展一次,每一次出队时即为该状态的最小代价。
3.问题每次扩展的代价是任意数值,等价于一般的最短路问题。
(1)使用优先队列
BFS
,时间复杂度 O ( N l o g N ) O(NlogN) O(NlogN)。每个状态被更新(入队)多次,只扩展一次,第一次出队时即为该状态的最小代价。
(2)使用迭代思想+普通的
BFS
,时间复杂度 O ( N 2 ) O(N^2) O(N2)。每个状态被更新(入队)、扩展(出队)多次,最终完成搜索后,记录数组中保存了最小代价。
3.双向BFS
在0x24
节中我们介绍了双向搜索的思想,双向BFS
与双向DFS
的思想完全相同。因为BFS
本身就是逐层搜索的算法,所以双向BFS
的实现更加自然、简便。以普通的求最少步数的双向BFS
为例,我们只需从起始状态、目标状态分别开始,两边轮流进行,每次各扩展一整层。当两边各自有一个状态在记录数组中发生重复时,就说明这两个搜索相遇了,可以合并得出起点到终点的最少步数。