• 转: 单调队列


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

    给定一个长度为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
    ", 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("
    ");
        
        for(int i= 0; i<n-k+1; i++)
        {
            if(i==0 )
                printf("%d", Max[0]);
            else
                printf(" %d", Max[i]);
        }
        printf("
    ");
    }
    
    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;
    }
  • 相关阅读:
    其实Unix很简单
    路由器硬件和操作系统软件关系之我见
    80后的我们
    虚拟机虚拟网卡作用
    [转]Cisco小失误,大麻烦
    DDWRT让我们的无线路由器用上Linux
    2011年全国大学生电子设计竞赛试题来自官网
    太网帧结构详解
    TCP/IP网络编程之四书五经
    四种以太网数据包详解
  • 原文地址:https://www.cnblogs.com/soTired/p/5399554.html
Copyright © 2020-2023  润新知