这是C++算法基础-数据结构专栏的第二十五篇文章,专栏详情请见此处。
引入
单调队列就是满足单调性的队列,它最经典的应用就是给定一个序列和一个窗口,使窗口在序列中从前向后滑动,求出窗口在每个位置时,其中元素的最大/小值。
下面我们就来讲单调队列的实现。
定义
单调队列就是满足单调性的队列结构,也就是说,其中的元素具有单调性,但是存储的方法和基本操作与队列一样。(其实,这里说的队列和我在队列的实现中讲解的队列有区别,稍后会提到)
过程
例题
我们从引入中所提到的一个经典问题来学习单调队列。
题目大意:给定一个序列和一个窗口,使窗口在序列中从前向后滑动,求出窗口在每个位置时,其中元素的最小值和最大值。
首先,我们把最小值和最大值分开来做,这里我们用求最小值来讲解。
仔细思考,在数组中,若有,且,那么很明显,当进入窗口中时,肯定不会是窗口中的最小值,而且只要还在窗口中,那么一定也在窗口中。从这点来看,对于求窗口中的最小值的过程中可能有贡献的数,在数组中是一个单调递增的序列(性质1)。
在窗口滑动一次后,首先就是判断序列最前面的元素是否出窗口,这时,如果我们在这个序列中存储数值,那就无法判断是否出窗口了,所以我们需要在序列中存储下标(性质2)。
然后要做的就是解决序列最后面的元素与当前滑进来的元素是否满足单调性的问题,如果序列最后面的一些元素不满足单调性,那么我们就将这些元素删除,最后将这个元素放在序列最后的位置(性质3)。
我们可以看出性质2、3,分别说明了数据可以从序列的尾部进,两端出,说明这个数据结构不是普通的队列,而是一种双向队列,再结合性质1,我们就想到了用单调队列这一数据结构。
单调队列主体过程
上面的例题让大家更加了解单调队列的性质和使用方法,这个章节我们就开始讲解单调队列的主体过程了。
首先,虽然单调队列本质上是双向队列,但它也是队列,双向队列只是在普通队列的基础上增加了一个插入数据的方向,单调队列的基本操作和普通队列基本是一样的,如果想了解具体内容,可以移步至我的这篇博客:队列的实现。
在这里就不再详细讲解,只讲解单调队列相比于普通的队列所特有的操作qwq
其实在例题中也能明白单调队列的过程:当数据进入单调队列时,先解决队首是否出窗口的问题,再解决队尾与当前元素是否满足单调性的问题,最后将当前元素下标插入队尾。
代码
下面给出单调队列的实现代码:
int que[N],hh=0,tt=-1;
for (int i=0;i<n;i++){
while(hh<=tt&&check_out(que[hh])) hh++;
while(hh<=tt&&check(que[tt],i)) tt--;
q[++tt]=i;
}
代码解释
第一行中,是用数组模拟的队列,表示队头,表示队尾;for循环内部是维护单调队列的过程;check_out()函数的作用是判断队头是否弹出;check()函数的作用是判断队列内维护的数据应该具有的性质(也就是对当前数据是否能入队列作出判断)。
上一篇-单调栈的实现 C++算法基础专栏文章 下一篇-KMP算法的实现
每周六更新一篇文章,内容一般是自己总结的经验或是在其他网站上整理的优质内容
点个赞,关注一下呗~