一、引入
广搜,即广度优先搜索算法,是搜索的一种。它和深搜的主要区别体现在访问次序上。我们先介绍一种数据结构,在简介一下广搜模板,最后讲解几道例题。
二、队列
队列(queue)是一种常见的数据结构。我们能从队尾插入元素,从对头取出元素。这恰恰和日常生活中的排队一样,只能从最后面开始排,排到最前面才轮到你。我们发现,如果开始结束队列都为空的话,最先放入的元素总是第一个被取出,第二个被放入的元素第二个被取出……最后一个被放入的元素最后一个被取出。因此,队列也有“先进先出表”之称。我们用两个变量分别记录对头和队尾的位置,类似于栈,我们就可以用数组实现队列了。
我们发现,实现队列的数组中每个位置最多只能用一次,因此会造成空间浪费。我们可以采用循环队列的方式,即当对列满了的时候,就可以把元素插入数组前端从而节省空间。我们也可以采用STL里的queque。
三、广度优先搜索算法
我们先把起点加入队列,每一步我们取队头。每一个决策点都可以引出若干个子决策点。不同于深搜,在枚举子决策点时我们不立即处理子决策点,而是把它加入队尾,等排在它前面的决策点都处理完后,也就是说当到它到达队头时再处理。从这里可以看出,广搜与队列有着很大的联系。
如果定义一个决策点的深度为其祖先决策点的个数。不难发现广搜是先把深度相同的决策点处理完的,这与深搜优先处理更深的决策点的思想不同,可以想象它处理决策点的次序是“横向”的,因此称之为广度优先搜索。
下面展示一段伪代码:
queue<node>q;
void bfs(){
node u,v;
q.push(起点); vi[起点]=1;
while(q.size()){
u=q.front();
for(v为u的子决策点){
更新v;
if(!vi[v]){
vi[v]=1;
q.push(v);
}
}
}
return;
}
四、经典例题
1.滑动窗口
有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
这是一道单调队列的模板题。以求最大值为例,我们弄一个队列存当前枚举到的元素及其前(k-1)个元素。每当枚举到后一个元素时,我们只需要把队头出队,再将这个元素入队就好了。但这种O(nk)的算法无法通过本题,我们便需要加以优化。如果对于i<j有a[i]<a[j],便会有i不比j更优。因为当i在队列时,j一定也在队列,最大值取不到a[i];当j在队列时,i不一定也在队列里。也就是说,j比i生存能力更强。因此我们在加入一个元素前,先把队尾比它小的元素剔除掉。这样,维护出来的是一个单调递减的队列,队头便是最大值。
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
int n,k,a[1000001];
deque<int>q;
/*这里用的是双端队列,队列的一种简单变形,
即能从两端同时加入、弹出或访问*/
int main(){
scanf("%d%d",&n,&k