码迷,mamicode.com
首页 > 其他好文 > 详细

转: 单调队列

时间:2016-04-16 23:05:31      阅读:318      评论:0      收藏:0      [点我收藏+]

标签:

我们从最简单的问题开始:

给定一个长度为N的整数数列a(i),i=0,1,...,N-1和窗长度k.

要求:

      f(i) = max{a(i-k+1),a(i-k+2),..., a(i)},i = 0,1,...,N-1

问题的另一种描述就是用一个长度为k的窗在整数数列上移动,求窗里面所包含的数的最大值。

解法一:

很直观的一种解法,那就是从数列的开头,将窗放上去,然后找到这最开始的k个数的最大值,然后窗最后移一个单元,继续找到k个数中的最大值。

这种方法每求一个f(i),都要进行k-1次的比较,复杂度为O(N*k)。

那么有没有更快一点的算法呢?

解法二:

我们知道,上一种算法有一个地方是重复比较了,就是在找当前的f(i)的时候,i的前面k-1个数其它在算f(i-1)的时候我们就比较过了。那么我们能不能保存上一次的结果呢?当然主要是i的前k-1个数中的最大值了。答案是可以,这就要用到单调递减队列。

单调递减队列是这么一个队列,它的头元素一直是队列当中的最大值,而且队列中的值是按照递减的顺序排列的。我们可以从队列的末尾插入一个元素,可以从队列的两端删除元素。

1.首先看插入元素:为了保证队列的递减性,我们在插入元素v的时候,要将队尾的元素和v比较,如果队尾的元素不大于v,则删除队尾的元素,然后继续将新的队尾的元素与v比较,直到队尾的元素大于v,这个时候我们才将v插入到队尾。

2.队尾的删除刚刚已经说了,那么队首的元素什么时候删除呢?由于我们只需要保存i的前k-1个元素中的最大值,所以当队首的元素的索引或下标小于 i-k+1的时候,就说明队首的元素对于求f(i)已经没有意义了,因为它已经不在窗里面了。所以当index[队首元素]<i-k+1时,将队首 元素删除。

 

从上面的介绍当中,我们知道,单调队列与队列唯一的不同就在于它不仅要保存元素的值,而且要保存元素的索引(当然在实际应用中我们可以只需要保存索引,而通过索引间接找到当前索引的值)。

为了让读者更明白一点,我举个简单的例子。

假设数列为:8,7,12,5,16,9,17,2,4,6.N=10,k=3.

那么我们构造一个长度为3的单调递减队列:

首先,那8和它的索引0放入队列中,我们用(8,0)表示,每一步插入元素时队列中的元素如下:

0:插入8,队列为:(8,0)

1:插入7,队列为:(8,0),(7,1)

2:插入12,队列为:(12,2)

3:插入5,队列为:(12,2),(5,3)

4:插入16,队列为:(16,4)

5:插入9,队列为:(16,4),(9,5)

。。。。依此类推

那么f(i)就是第i步时队列当中的首元素:8,8,12,12,16,16,。。。

 

poj2823  n, k分别代表数组数的个数 和 每k个数找一个最值, 输出最值序列, 0(n)的单调队列 ;

Window positionMinimum valueMaximum value
[1  3  -1] -3  5  3  6  7  -1 3
 1 [3  -1  -3] 5  3  6  7  -3 3
 1  3 [-1  -3  5] 3  6  7  -3 5
 1  3  -1 [-3  5  3] 6  7  -3 5
 1  3  -1  -3 [5  3  6] 7  3 6
 1  3  -1  -3  5 [3  6  7] 3 7
#include <math.h>
#include <iostream>
const int MAX = 1e6+1;

int a[MAX];   //存储数据; 
int q[MAX];   //队列;  
int p[MAX];   //存储啊a[i]中的下标; 
int Min[MAX]; //输出最小; 
int Max[MAX]; //输出最大; 
int n, k;
using namespace std;

void get_min()
{
    int i;
    int head=1, tail =0;
    for(i=0; i< k-1; i++)  //先把两个入队 ;  
    {
        while(head <=tail &&q[tail] >= a[i])  //队尾元素大于要输入的数 ; 
            --tail;
        q[++tail]=a[i];
        p[tail]=i;
    }
    
    for( ;i <n; i++)
    {
        while(head<= tail && q[tail] >= a[i])
            --tail;
        q[++tail] =a[i]; 
        p[tail]= i;
        while(p[head] < i-k+1) //判断数是否过时, 即窗口是否已经划过这个数,  从0开始计数的; 
        {
            head++;    
        }
        //printf("%d %d\n", i, head);
        Min[i-k+1]= q[head];
    }
}

void get_max()
{
    int i;
    int head=1, tail =0;
    for(i=0; i< k-1; i++)
    {
        while(head <=tail &&q[tail] <= a[i]) //队尾元素小于要插入的值 ; 
            --tail;
        q[++tail]=a[i];
        p[tail]=i;
    }
    
    for( ; i <n; i++)
    {
        while(head<= tail && q[tail] <= a[i]) //队尾元素小于要插入的值 ;  
            --tail;
        q[++tail] =a[i]; 
        p[tail]= i;
        while(p[head] < i-k+1)
        
        {
            head++;    
        }
        Max[i-k+1]= q[head];
    }
}

void output()
{
    int i;
    for(int i=0; i< n-k+1; i++)
    {
        if(i== 0)
            printf("%d", Min[0]); 
        else
            printf(" %d", Min[i]);
    }
    printf("\n");
    
    for(int i= 0; i<n-k+1; i++)
    {
        if(i==0 )
            printf("%d", Max[0]);
        else
            printf(" %d", Max[i]);
    }
    printf("\n");
}

int main()
{
    scanf("%d%d", &n, &k);
    
    for(int i=0; i< n; i++)
    {
        scanf("%d", &a[i]);    
    }
    get_min();
    get_max(); 
    output();
    return 0;
}

 

转: 单调队列

标签:

原文地址:http://www.cnblogs.com/fengshun/p/5399554.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!