单调队列

单调队列的性质:

(1)队列中的元素按照大小递增或递减

(2)可以在队列尾部添加元素,可以在队列头部或尾部删除元素

 

如何维护单调队列呢,以单调递增序列为例:

1、如果队列的长度一定,先判断队首元素是否在规定范围内,如果超范围则增长队首

2、每次加入元素时和队尾比较,如果当前元素小于队尾且队列非空,则减小尾指针,队尾元素依次出队,直到满足队列的调性为止

要特别注意头指针和尾指针的应用。

 

解决问题时,n 个元素 平均每个元素都会进入队列和从队列中出来一次,因此平摊下来复杂度为O(n)。

典型问题1: poj 2823 Sliding window

  对于给定的一个数组A, 以及A的长度n和窗口长度k。输出窗口在数组A上滑动时,窗口区域内的元素的最大值和最小值,即##{A[0], A[1],... A[k - 1]}## , ## {A[1], A[2], ....A[k] } ##, .... ##{A[n-k], A[n-k+1], .... A[n-1]}## 这一系列的数组中的最大元素和最小元素。

分析:

  思考这个问题时,肯定从 A[0], A[1],.. A[k-1] 中的最大值和最小值 推算出 A[1], A[2], ... A[k] 的最大值和最小值。以最大值为例, 分为两种情况,情况1是 A[0], A[1], ... A[k-1] 中的最大值不是 A[0], 则只需要将 A[0], A[1] ... A[k-1] 的最大值与 A[k]进行比较,选择他们两个中的最大值即可;情况2是 A[0] 即为 A[0], A[1], ... A[k - 1] 的最大值,此时 A[0]不属于窗口范围,因此,需要将 A[0], A[1], ... A[k - 1] 中的第二大元素与 A[k]进行比较选择最大值 ...... 通用一点考虑,可以 将 A[0], A[1], ... A[k-1] 这个窗口内的k 个元素按照大小排序,形成单调递减队列,从队列头部取出的元素即为这k个元素中的最大值,队首下一个元素即为这个队列中的第二大元素 ....

  使用单调队列解决该问题,需要保持队列的长度 小于等于 k, 这样才能保证队列中的元素都位于窗口范围之内。 为了求出窗口内的最大值,需要使用递减队列,求出最小值需要使用递增队列。以求最大值为例,需要使用一个队列 inc_queue, 其中存放着 数组 A 中元素的索引,队列头部 front_inc 的元素 inc_queue[front_inc] 即为窗口中的最大值的索引,尾部 tail_inc 的元素 inc_queue[tail_inc] 为当前处理的元素 A[i] 的索引 i 。

  为了满足单调队列的条件1 ,需要在每次将当前元素 A[i] 插入 队列中时比较A[i] 和 A[inc_queue[tail_inc]] ,如果此时队列尾部的元素大于A[i],则直接将A[i]放在尾部即可,否则需要将tail_inc --, 找到A[i]在队列中合适的位置再插入。同时队列的尾部 tail_inc 更新到队列中插入A[i]的位置,这样做会导致队列中 新的tail_inc之后到原来的tail_inc之间的 元素失效,但是这样做不影响结果的正确性,因为新插入的队尾元素 inc_qeueue[tail_inc] = i ,是当前队列中(窗口中)最新的元素,队列中原来的由于tail_inc 减小而失效的元素的值早于 i 入队,在窗口中 i 的左侧,这样在窗口滑动的时候,肯定要早于 i 失效,而且如果需要在这些元素(因为tail_inc的移动而失效的元素)和 i 中选择最大的元素,那也肯定选择 i , 因为 A[i] 的值大于这些索引位置的数组A的元素值。

  为了 限制 单调队列的长度 小于等于k, 在每次弹出队列首部元素的时候,需要指定要弹出的首部元素在数组A中位于当前元素 (下标为 i )之前 k 个范围内 (i - k <= inc_qeueue[front_inc]) 。

源码如下:(在poj中使用 g++ 超时,而使用c++则不会 .....)

#include<stdio.h>
#define MAX_NUM 1000005
int number[MAX_NUM];
int inc_queue[MAX_NUM];
int dec_queue[MAX_NUM];

int main(){
    int n, k;
    scanf("%d %d", &n, &k);
    getchar();
    for(int i = 0; i < n; i ++){
        scanf("%d", &number[i]);
    }
    int front_inc = 0, front_dec = 0, tail_inc = -1, tail_dec = -1;
    for(int i = 0; i < k; i ++){
        while(front_inc <= tail_inc && number[inc_queue[tail_inc]] >= number[i]){ //找到当前元素 A[i]的合适的位置进行插入
            tail_inc --;
        }
        inc_queue[++ tail_inc] = i;

        while(front_dec <= tail_dec && number[dec_queue[tail_dec]] <= number[i]){
            tail_dec --;
        }
        dec_queue[++ tail_dec] = i;
    }
    printf("%d ", number[inc_queue[front_inc]]);

    for(int i = k; i < n; i ++){
        while(front_inc <= tail_inc && number[inc_queue[tail_inc]] >= number[i]){
            tail_inc --;
        }
        inc_queue[++ tail_inc] = i;
        
        while(i - k >= inc_queue[front_inc] && front_inc < tail_inc){   //控制窗口长度
            front_inc ++;
        }
        printf("%d ", number[inc_queue[front_inc]]);
    }
    printf("\n");
    
    printf("%d ", number[dec_queue[front_dec]]);
    for(int i = k; i < n; i ++){
        while(front_dec <= tail_dec && number[dec_queue[tail_dec]] <= number[i]){
            tail_dec --;
        }
        dec_queue[++ tail_dec] = i;
        while(i - k >= dec_queue[front_dec] && front_dec < tail_dec){
            front_dec ++;
        }
        printf("%d ", number[dec_queue[front_dec]]);
    }
    printf("\n");
    return 0;
}

 

posted @ 2015-06-03 18:39  农民伯伯-Coding  阅读(216)  评论(0编辑  收藏  举报