单调数据结构总结

单调数据结构是一种在我看来很神奇的数据结构,其中包括单调栈和单调队列,但其实它们的思想几乎是完全一致的,可以说单调队列就是单调栈的升级版本。

我们知道单调队列是可以由两端出队的,也就是双端队列 std::deque ;而单调栈就是普通的栈,但是 std::stack 的底层就是由 std::deque 来实现的,因此得出结论,单调队列就是单调栈的升级版本,因此,让我们先来讨论关于单调栈的事情。

1. 单调栈

单调栈的核心只有一句话:排除所有显然不可能的决策

例题: 单调栈模板题目

题意:求一个序列中每个数右边第一个大于它的数。

我们建立一个栈,依次让每个数入栈。由于记录的是每一个数右边的第一个大于它的数字,因此需要采用倒序遍历每一个数。

单调栈的核心:当每一次要将一个数入栈时,显然,当前在栈内的元素全部在这个数的右端。考虑到我们只记录在它右边第一个大于它的数,因此不断循环判断栈顶元素,如果栈顶元素还没有当前所求的数大,显然它绝对不会成为答案,因此我们就可以将它直接弹出。显然地,当遇到栈顶元素已经大于当前判断的数了,又结合到我们的入栈次序使得栈里的元素的位置绝对递增,再向后遍历到的不会成为最优解,那当前的数显然就成为了第一个大于所判断的数字的数,记录答案。

形象地描绘步骤:

记当前遍历到的数是 k ,我们可以循环判断如果当前的栈顶元素t,如果t k,则就弹出t,直到 t > k结束循环。循环结束时,栈顶的元素就是所求的答案,再将需要入栈的数字放入栈中。

下来给出参考程序

#include<stdio.h>
#define maxn 100005
int stk[maxn], a[maxn];
int ans[maxn];
int n, top;
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    stk[++top] = n + 1;//要先压入一个元素,防止爆栈
    for(int i = n; i; i--){//倒序遍历
        while(top && a[stk[top]] <= a[i])//栈中还有元素并且栈顶元素不大于当前元素
            top--;//出栈
        if(top)//如果栈里还有元素
            ans[i] = stk[top];//记录答案;由于一开始就是倒序遍历,因此越左边的数越晚入栈,因此栈顶就是答案
        stk[++top] = i; //将当前元素入栈
    }
    for(int i = 1; i <= n; i++)
        printf("%d\n", ans[i]);//输出
    return 0;
}

分析时间复杂度:

最外层的遍历是O(n)的,每个元素最多入栈、出栈一次,因此复杂度还是O(n)的,综上所述,单调栈的时间复杂度是O(n),大大优于能够解决同样问题的数据结构。

2. 单调队列

单调队列其实就是单调栈的升级版本。回顾单调栈的例题,我们所求的是每个数在序列当中在它右边最大的数,单调队列不过是添加了一个限制——所求的答案和原数距离不超过k。因此单调队列的思想便显然了——在遍历出队之后检查队首与队尾的距离,超过则从队首出队即可。其它在单调栈的基础上不需要做任何改动。

上代码——

int head = 1, tail = 0;
    for(int i = 1; i <= n; i++){
        while(head <= tail && a[q[tail]] > a[i])
            tail--;//这一步是不满足可行性时的出队
        while(head <= tail && q[head] + k <= i)//相对于单调栈只多了这一步
            head++;//这一步是不满足距离要求的出队
        q[++tail] = i;//这一步是遍历时的入队
        ans[i] = q[head];
    }
posted @   长安19路  阅读(22)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示