單調隊列

定義

       單調隊列就是具有單調性質的隊列,即單調遞減或單調遞增的隊列。

用途

       在長度爲n的數組中,從1到n-m+1分別爲起點的連續的m個區間長度中求出最大值或最小值

處理過程

       進隊操作:將進隊的元素爲e,從隊尾往前掃描,直到找到一個不大於e的元素d,將e放在d之後,捨棄e之後的所有元素;如果沒有找到這樣一個d,則將e放在隊頭(此時隊列裏只有這一個元素)。

       出隊操作:取隊首元素出隊

基本思想

       維護隊首元素作爲答案,去掉多餘的元素(維護單調性)。

時間複雜度

       每個元素最多進隊一次,出隊一次,攤排分析下來仍然是 O(1),總複雜度爲複雜度O(n)。

例題

       http://poj.org/problem?id=2823(單調隊列模板)

       題意:輸入n和m,n代表數組的長度,m代表連續區間的長度,找出每個連續m個區間長度中的最大值和最小值。接下來輸入n個數。

       思路:這個問題相當於一個數據流(數列a)在不斷地到來,而數據是不斷過期的,相當於我們只能保存有限的數據(sliding window中的數據,此題中就是窗口的寬度w),對於到來的查詢(此題中查詢是每時刻都有的),我們要返回當前滑動窗口中的最大值\最小值。注意,元素是不斷過期的。

解決這個問題可以使用一種叫做單調隊列的數據結構,它維護這樣一種隊列:

a)從隊頭到隊尾,元素在我們所關注的指標下是遞減的(嚴格遞減,而不是非遞增),比如查詢如果每次問的是窗口內的最小值,那麼隊列中元素從左至右就應該遞增,如果每次問的是窗口內的最大值,則應該遞減,依此類推。這是爲了保證每次查詢只需要取隊頭元素。

b)從隊頭到隊尾,元素對應的時刻(此題中是該元素在數列a中的下標)是遞增的,但不要求連續,這是爲了保證最左面的元素總是最先過期,且每當有新元素來臨的時候一定是插入隊尾。

滿足以上兩點的隊列就是單調隊列,首先,只有第一個元素的序列一定是單調隊列。

那麼怎麼維護這個單調隊列呢?無非是處理插入和查詢兩個操作。

對於插入,由於性質b,因此來的新元素插入到隊列的最後就能維持b)繼續成立。但是爲了維護a)的成立,即元素在我們關注的指標下遞減,從隊尾插入新元素的時候可能要刪除隊尾的一些元素,具體說來就是,找到第一個大於(在所關注指標下)新元素的元素,刪除其後所有元素,並將新元素插於其後。因爲所有被刪除的元素都比新元素要小,而且比新元素要舊,因此在以後的任何查詢中都不可能成爲答案,所以可以放心刪除。

對於查詢,由於性質b,因此所有該時刻過期的元素一定都集中在隊頭,因此利用查詢的時機刪除隊頭所有過期的元素,在不含過期元素後,隊頭得元素就是查詢的答案(性質a),將其返回即可。

      代碼:

#include <iostream>
#include<cstdio>
using namespace std;
const int Max = 1000004;
int n,m,a[Max],v[Max],mn[Max],mx[Max],head,tail;//v代表隊列,存放的是數組a的下標
void getmin()
{
    head = 1;tail = 0;//代表隊列的頭和尾
    for(int i = 1; i < m; i++)//先把隊列中初始的值確定
    {
        while(head <= tail && a[v[tail]] >= a[i]) tail--;
        v[++tail] = i;
    }
    for(int i = m; i <= n; i++)
    {
        while(head <= tail && a[v[tail]] >= a[i]) tail--;//如果要插入的數比隊尾的數還要大的話,就說明對答案沒有影響,就可以刪除此元素
        v[++tail] = i;
        while(v[head] < i-m+1) head++;
        //mn[i-m+1] = a[v[head]];
        printf("%d ",a[v[head]]);//輸出隊首元素,即最小值
    }
    cout << endl;
}
void getmax()//同理可得
{
    head = 1;tail = 0;
    for(int i = 1; i < m; i++)
    {
        while(head <= tail && a[v[tail]] <= a[i]) tail--;
        v[++tail] = i;
    }
    for(int i = m; i <= n; i++)
    {
        while(head <= tail && a[v[tail]] <= a[i]) tail--;
        v[++tail] = i;
        while(v[head] < i-m+1) head++;
        //mx[i-m+1] = a[v[head]];
        printf("%d ",a[v[head]]);
    }
    cout << endl;
}
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    getmin();
    getmax();
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章