初次学习单调队列与单调栈

单调栈 ,单调队列

一、单调栈
单调栈是指一个栈内部的元素是具有严格单调性的一种数据结构,分为单调递增栈和单调递减栈。
单调栈有两个性质
1.满足从栈顶到栈底的元素具有严格的单调性
2.满足栈的后进先出特性越靠近栈底的元素越早进栈

元素进栈过程
对于一个单调递增栈来说 若当前进栈的元素为 a 如果a < 栈顶元素则直接将a 进栈 如果 a >= 当前栈顶元素则不断将栈顶元素出栈知道满足 a < 栈顶元素

模拟一个数列构造一个单调递增栈
进栈元素分别为3,4,2,6,4,5,2,3。
首先 3 进栈 变为 {3};
然后 4 进栈 先让 3 出栈 再进栈 变为 {4};
2 进栈 变为 {4,2};
对于 6 先让 4,2 出栈 再让 6 进栈 {6};
4 进栈 {6,4};
让4 出栈 5进栈 {6,5};
然后2进 2出 3进 变为 {6,5,3};
由于单调栈只能对栈顶位置进行操作 所以一般应用于只有对数组有一边有限制的地方
实现用STL的栈实现即可 模拟栈也行

二、.单调队列
单调队列是指一个队列内部的元素具有严格单调性的一种数据结构,分为单调递增队列和单调递减队列。

单调队列满足两个性质
1.单调队列必须满足从队头到队尾的严格单调性。
2.排在队列前面的比排在队列后面的要先进队。

元素进队列的过程对于单调递增队列,对于一个元素a 如果 a > 队尾元素 那么直接将a扔进队列 如果 a <= 队尾元素 则将队尾元素出队列 知道满足 a 大于队尾元素即可;

实现用 stl 的双端队列即可。

由于双端队列即可以在队头操作也可以在队尾操作那么这样的性质就弥补了单调栈只能在一遍操作的不足可以使得其左边也有一定的限制。

三、双端队列deque的用法
1、deque的常用成员函数

deque<int> deq;
deq[ ]:用来访问双向队列中单个的元素。
deq.front():返回第一个元素的引用。
deq.back():返回最后一个元素的引用。
deq.push_front(x):把元素x插入到双向队列的头部。
deq.pop_front():弹出双向队列的第一个元素。
deq.push_back(x):把元素x插入到双向队列的尾部。
deq.pop_back():弹出双向队列的最后一个元素。

2、遍历deque容器

    for(int i = 0; i < d.size(); i ++)
        cout<<d[i]<<" ";
    cout<<endl;

    deque<int>::iterator it;
    for(it = d.begin(); it != d.end(); it ++)
        cout<<*it<<" ";
    cout<<endl;

    deque<int>::reverse_iterator rit;
    for(rit = d.rbegin(); rit != d.rend(); rit ++)
        cout<<*rit<<" ";
    cout<<endl;

3、deque的一些特点

·1>支持随机访问,即支持[ ]以及at(),但是性能没有vector好。
2>可以在内部进行插入和删除操作,但性能不及list。
3>deque两端都能够快速插入和删除元素,而vector只能在尾端进行。
4>deque的元素存取和迭代器操作会稍微慢一些,因为deque的内部结构会多一个间接过程。
5>deque迭代器是特殊的智能指针,而不是一般指针,它需要在不同的区块之间跳转。
6>deque可以包含更多的元素,其max_size可能更大,因为不止使用一块内存。
7>deque不支持对容量和内存分配时机的控制。
8>在除了首尾两端的其他地方插入和删除元素,都将会导致指向deque元素的任何pointers、references、iterators失效。不过,deque的内存重分配优于vector,因为其内部结构显示不需要复制所有元素。
9>deque的内存区块不再被使用时,会被释放,deque的内存大小是可缩减的。不过,是不是这么做以及怎么做由实际操作版本定义。
10>deque不提供容量操作:capacity()和reverse(),但是vector可以。

四、滑动窗口(单调队列)
luogu1886:https://www.luogu.org/problem/show?pid=P1886
思想:
(1)滑动窗口就是一个数组的长度为k的一部分;
(2)建立一个双端队列,该队列是有序队列,队头的元素最小;
(3)进队列:如果当前元素比队尾元素大,或者队列为空,则直接进队;
如果当前元素比队尾元素小,则队尾元素出队,直至当前元素大于队尾元素或队列为空,然后当前元素入队;
(4)出队列:取每个窗口对应的最小值的时候,要保证队头元素在窗口里面;
队头出列时,判断队头元素是否在窗口里面,如果在则直接输出;否则,则一直出队头元素,直至所出的队头元素在窗口里面,然后输出。
(5)队列中存取的是元素的下标,可以节省空间。
(6)一个单调队列,末端插入,弹出,首端只弹出,不插入。队首始终为最小值。
(7)如果新入队的元素与队头元素的距离>=滑动窗口的宽度,则队头元素出队列。

讲解:转自:https://blog.csdn.net/yyt330507870/article/details/53415900
单调队列是一种特殊的队列,它能在队列两端进行删除操作,并始终维护队列保持一种单调性。
以求窗口最小值为例,我们需要维护一个内部元素值递减的队列来模拟滑动窗口,使每一次查询取队首元素即为答案。如何维护呢?有以下两种操作:

1.从队尾插入元素:当有新元素需要入队时,让它与当前队尾元素进行比较,若它小于等于当前队尾元素(即破坏了原队列的单调性),那么删除队尾元素,并继续比较队尾与新元素,直到找到一个队尾大于新元素时,将新元素插入到队尾。被删除的元素既比新元素大,又会比新元素先滑出窗口,因此肯定不会成为答案。这个操作不断维护了队列中的最值。

2.删除队首元素:由于序列中的元素当且仅当在滑动窗口时有效,因此,当队首元素不在滑动窗口内时,就删除队首元素。这个操作维护了数据的临时性。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = 1e6 + 5;
int a[N],MIN[N],MAX[N];
deque<int> queMin;
deque<int> queMax;
int n,k;

void getMin()
{
    queMin.clear();
    for(int i = 1;i <= k;++i)
    {
        while(!queMin.empty() && a[i] <= a[queMin.back()]){
                queMin.pop_back();
        }
        queMin.push_back(i);
    }
    int tot = 0;
    MIN[++tot] = a[queMin.front()];
    for(int i = k + 1;i <= n;++i)
    {
        while(!queMin.empty() && a[i] <= a[queMin.back()]){
            queMin.pop_back();
        }
        queMin.push_back(i);
        while(!queMin.empty() && queMin.front() < i - k + 1){
            queMin.pop_front();
        }
        MIN[++tot] = a[queMin.front()];
//        deque<int>::iterator it;
//        for(it = queMin.begin(); it != queMin.end(); it ++)
//            cout<<*it<<" ";
//        cout<<endl;
    }
}

void getMax()
{
    queMax.clear();
    for(int i = 1;i <= k;++i)
    {
        while(!queMax.empty() && a[i] >= a[queMax.back()]){
                queMax.pop_back();
        }
        queMax.push_back(i);
    }
    int tot = 0;
    MAX[++tot] = a[queMax.front()];
    for(int i = k + 1;i <= n;++i)
    {
        while(!queMax.empty() && a[i] >= a[queMax.back()]){
            queMax.pop_back();
        }
        queMax.push_back(i);
        while(!queMax.empty() && queMax.front() < i - k + 1){
            queMax.pop_front();
        }
        MAX[++tot] = a[queMax.front()];
    }
}

int main()
{
    while(~scanf("%d %d",&n,&k))
    {
        for(int i = 1;i <= n;++i)
        {
            scanf("%d",&a[i]);
        }
        getMin();getMax();
        for(int i = 1;i <= n - k + 1;++i)
        {
            if(i == n - k + 1){
                printf("%d\n",MIN[i]);
            }
            else{
                printf("%d ",MIN[i]);
            }
        }
        for(int i = 1;i <= n - k + 1;++i)
        {
            if(i == n - k + 1){
                printf("%d\n",MAX[i]);
            }
            else{
                printf("%d ",MAX[i]);
            }
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_36386435/article/details/81748309