简单易懂,单调栈和队列

本文详细介绍了两种基本数据结构——栈和队列的基本操作及应用。重点讲解了栈和队列的实现方式,并探讨了它们在解决特定问题时的应用场景,如单调栈用于寻找最接近的较小值,单调队列用于滑动窗口最大值问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

”明月如霜,好风如水,清景无限 “

文远已经很久没更新了,中秋补一篇吧。最常见的两种数据结构,栈和队列,也就是先进后出和先进先出。从这一点来看,似乎这两个数据结构挺简单的,但难点在于何时使用它,也就是识别出什么时候该用这样的数据结构。

* stack基础的基础操作:

  • 如果用数组模拟栈的话,需要数组int stk[N] , hh = -1;其中hh = -1待定,也就是初始情况下栈为空,认为stk[0]为栈的首元素。若改为0,则认为stk[1]为栈的首元素。

  • 入栈:

stk[++hh] = x;
  • 出栈:
--hh;
  • 访问栈顶:
cout<<stk[hh]<<endl;
  • 判断栈空:
if(hh < 0) ----> 栈空

* 单调栈,顾名思义,具有单调性。而有单调性最大的好处不外乎降低复杂度。相当于排好序了。

  • 最常见应用:找到数组中某数,比其小且index最接近的数。(eg:对长度为N的数组,输出每个数左边第一个比它小的数,没有则输出-1)

  • 分析:暴力做,比较简单。双指针:i++循环,j = i并–循环。找到小于的数 ,也就是a[j],就退出内循环。

  • 用单调栈做,如何保证栈内单调?输出的答案肯定是栈顶(栈只能输出栈顶)。其实也就是两方面,何时入栈,何时出栈。首先栈顶肯定是最小值。

  • 何时出栈:为保证栈顶最小值,新来的小于等于栈顶,那就把内元素给踢出去。很明显这是一个while,也就是持续踢出去

  • 何时入栈:新来的严格大于栈顶(则栈顶为离新来的最近的小于它的元素),而且必须入栈在循环末尾。因为需要保证栈内元素是新来的以前的元素(即数左边比它小)。

  • 代码

for(int i = 0 ; i < n ;++i){
    while(hh >= 0 && stk[hh] >= a[i]) {
        cout<<"出栈元素:"<<stk[hh]<<endl;
        --hh;
    }
    if(hh >= 0) cout<<stk[hh]<<" ";
    else cout<<"-1 ";
    // if(hh < 0 || a[i] > a[i - 1]) {////栈空则入栈
    // if(hh < 0 || a[i] > stk[hh]) {
        stk[++hh] = a[i];
        cout<<"进栈元素:"<<stk[hh]<<endl;
    // }
  • 结果:

图片

* queue基础的基础操作:

  • 如果用数组模拟队列的话,需要数组int q[N] , hh = 0 , tt = -1;其中队首hh = 0 , 队尾tt = -1;也就是初始情况下队列为空。

  • 入队:

q[++tt] = x;
  • 出队首:
++hh;
  • 出队尾
--tt;
  • 访问队首:
cout<<q[hh]<<endl;
  • 判断队空:
if(hh > tt) ----> 队空

* 单调队列,顾名思义,具有单调性。而有单调性最大的好处不外乎降低复杂度。相当于排好序了。

  • 最常见应用:找到数组中某个滑动窗口的最小值或者最大值,例如滑动窗口长度为3。(eg:对长度为N的数组,输出长度为3的滑动窗口的最大值)

  • 分析:暴力做,比较简单。双指针:i++循环,j = i + 2并++循环。找到[i , j ]内最大值就退出内循环。

  • 用单调队列做,如何保证栈内单调?输出的答案肯定是队首(队列一般都是队首出队)。其实也就是两方面,何时入队,何时出队。首先队首肯定是最大值。

  • 何时出队:为保证队首最大值,新来的大于等于队尾,否则把队尾元素给踢出去。很明显这是一个while,也就是持续踢出去。另一种则是虽然是最大值,但是滑窗左边界已经离开了最大值,则从队首出队。

  • 何时入队:新来的严格小于队尾。(其实还是意思是队内从hh到tt严格下降,而新来的能直接入队尾了,就是因为while把队尾小的都踢出去了)

代码

vector<int> res;
hh = 0 , tt = -1;
/////队首内存的是a[i --- i + k -1]的最大值下标 
for(int i = 0 ; i < n ;++i){
/////出队:队非空 且 对应新窗口, 窗口左边已过最大值下标 
    if(hh <= tt && i - k + 1 > q[hh]){
        cout<<"出队出滑窗的首元素(max):"<<i - k + 1<<" "<<q[hh]<<" "<<a[q[hh]]<<endl;
        ++hh; 
    }
    ///////保证队尾 大于 a[i]
    while(hh <= tt && a[q[tt]] <= a[i] ){
        cout<<"出队尾元素:"<<a[q[hh]]<<endl; 
        --tt;
    }
    q[++tt] = i;
    cout<<"入队尾元素:"<<a[i]<<endl; 
    if (i - k + 1 >= 0 && hh <= tt){
        res.push_back(a[q[hh]]);
        cout<<"输出当前滑窗最大值:"<<a[q[hh]]<<endl;
    }
}
for(auto i : res){
    cout<<i<<" ";
}
printf("\n");

结果:
图片

获取源码可点击阅读原文,对你有帮助请点赞关注支持一下。

END

作者:不爱跑马的影迷不是好程序猿

   喜欢的话请关注点赞👇 👇👇 👇        

在这里插入图片描述

壹句: 昨夜星辰昨夜风

阅读原文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值