静态栈和队列(模板)

模拟栈

栈是一种常用的数据结构
他的逻辑是先进后出
就比如我们几个人走到一个死胡同,先进去的人只能在里面等后面进去的人出去才能出来
这里用数组实现一个静态的栈

Acwing:828.模拟栈

实现一个栈,栈初始为空,支持四种操作:

push x – 向栈顶插入一个数 x ;
pop – 从栈顶弹出一个数;
empty – 判断栈是否为空;
query – 查询栈顶元素。

现在要对栈进行 M个操作,其中的每个操作 3 和操作 4都要输出相应的结果。

输入格式
第一行包含整数 M,表示操作次数。

接下来 M行,每行包含一个操作命令,操作命令为 push x,pop,empty,query 中的一种。

输出格式

对于每个 empty 和 query 操作都要输出一个查询结果,每个结果占一行。
其中,empty 操作的查询结果为 YES 或 NO,query 操作的查询结果为一个整数,表示栈顶元素的值。

数据范围

1≤M≤100000,
1≤x≤109

所有操作保证合法。

输入样例:

10
push 5
query
push 6
pop
query
pop
empty
push 4
query
empty

输出样例:

5
5
YES
4
NO

#include <iostream>
using namespace std;
const int N=100010;
int stk[N],tt=0;//栈和栈顶指针(和书本写法还是有yi点区别的 但是最终实现效果一样)

void push(int x)//压栈
{
    stk[++tt]=x;
}

void pop()//出栈
{
    tt--;
}

int head()//读取栈顶元素
{
    return stk[tt];
}

bool isempty()//判断栈是否为空
{
    if(tt)return false;
    return true;
}

int main(void)
{
    int m;
    cin>>m;
    while(m--)
    {
        
        string op;
        cin>>op;
        if(op=="push")
        {
            int x;
            cin>>x;
            push(x);
            
        }
        
        if(op=="pop")
        {
            pop();
        }
        
        if(op=="empty")
        {
            if(isempty())puts("YES");
            else puts("NO");
        }
        
        if(op=="query")
        cout<<head()<<endl;
        
    }
    
    return 0;
    
}

单调栈

插入元素的同时保证栈里有一定的单调性

比如我们遇到这样的问题: 对于一个数组 遍历每个元素时候,要求找到第一个比他小的元素,用朴素的做法就是写一个双重循环,第一个循环遍历数组元素,第二个循环就是从后往前遍历,从而找到答案。
有没有什么办法可以优化一下呢,我们发现有的数始终用不到,举个例子,546,现在我们要找6的第一个比他小的数,答案就是4,因为5在4的前面,只要4在的一天,5永远没有出头之日,因此5就没用了。
我们可以用一个栈来实现这个单调性, 就是让那些所谓"在4前面没用的元素"给弹掉,然后只留下所谓的"4",然后会发现这样做以后,栈里面留下的都是比新插入元素小的,这样就实现了单调性!
下面看例题

Acwing:830. 单调栈

给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
输入格式

第一行包含整数 N,表示数列长度。

第二行包含 N个整数,表示整数数列。

输出格式

共一行,包含 N个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1。

数据范围
1≤N≤105
1≤数列中元素≤109

输入样例:

5
3 4 2 7 5

输出样例:

-1 3 -1 2 2

#include <iostream>
using namespace std;
const int N = 100010;

int stk[N],tt=0;

int main(void)
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        int x;
        scanf("%d",&x);
        while(tt&&stk[tt]>=x)tt--;
        //如果比x大 弹出了 反正后面x放进来 x就是比这些弹出去的更优 x在这些不弹出去留着也没用
        if(tt)printf("%d ",stk[tt]);
        else printf("-1 ");
        stk[++tt]=x;
    }
    
    return 0;
}



还是有点模糊的可以看一下这个

  /*
        基本思想:
①先用暴力作法发现要用两重循环,时间复杂度高,为了降低时间复杂度要发现一种性质。会发现当输入一个新的数时,前面比它大的数都不会再用到。
②可用栈,在每次输入的数入栈前,将栈中比它大的数都删掉,使栈变成从栈底到栈顶单调递增的单调栈,此时的栈顶便是比它小且离它最近的数。

        朴素算法是i遍历一遍 然后内层循环往前遍历 找到第一个比它小的 break
        这个用单调队列 
        3 4 2 7 5
        = = = = =
        i j x
        如果i<j a[i]>=a[j] 且都<x 那么a[i]永远不会被用到
        所以可以把a[i]弹出 后面插入更好的x 这样是也保证了是单调的
        就是
        3 存下来 输出-1 当前队列 3
        3<4  输出3  4 存下来 当前队列 3 4
        4>2 pop 3>2 pop 输出-1 2存下来 当前队列 2
        2<7 2   7存下来 当前队列 2 7 
        7>5 pop 2<5 输出2 5存下来 当前队列 2 5
        
        
             
        
        */

模拟队列

队列是一种常用的数据结构
他的逻辑就是先进先出
就像排队出门口一样 排前面的可以先出去 排后面的后出去
这里用数组实现一个静态的队列

Acwing:829. 模拟队列

实现一个队列,队列初始为空,支持四种操作:

push x – 向队尾插入一个数 x;
pop – 从队头弹出一个数;
empty – 判断队列是否为空;
query – 查询队头元素。

现在要对队列进行 M
个操作,其中的每个操作 3 和操作 4都要输出相应的结果。

输入格式

第一行包含整数 M,表示操作次数。

接下来 M行,每行包含一个操作命令,操作命令为 push x,pop,empty,query 中的一种。

输出格式

对于每个 empty 和 query 操作都要输出一个查询结果,每个结果占一行。

其中,empty 操作的查询结果为 YES 或 NO,query 操作的查询结果为一个整数,表示队头元素的值。

数据范围

1≤M≤100000,
1≤x≤109,
所有操作保证合法。

输入样例:

10
push 6
empty
query
pop
empty
push 3
push 4
pop
query
push 6

输出样例:

NO
6
YES
4

#include <iostream>
using namespace std;
const int N=100010;
int q[N],hh,tt=-1;//队列 队头指针 队尾指针
void push(int x)//进队
{
    q[++tt]=x;
}

void pop()//出队
{
    hh++;
}

bool isempty()//判空
{
    if(hh<=tt)return false;//队头在前面 队尾在后面 这样就不为空
    
    return true;
   
}

int head()//读取队头元素
{
    return q[hh];
}
int main(void)
{
    int m;
    cin>>m;
    while(m--)
{
    string op;
    cin>>op;
    
    if(op=="push")
    {
        int x;
        cin>>x;
        push(x);
    }
    if(op=="pop")
    {
        pop();
    }
    
    if(op=="empty")
    {
        if(isempty()) puts("YES");
        else puts("NO");
    }
    if(op=="query")
    cout<<head()<<endl;
    
}
    return 0;
    
}

单调队列(双端队列deque)

让队列元素保持单调性

在这里插入图片描述

给定一个数组,每次在一个范围里面找到他的最小值(范围会改变)

接下来用一个例题来体会一下单调队列是如何运用的 道理和单调栈差不多

Acwing 154.滑动窗口

给定一个大小为 n≤106

的数组。

有一个大小为 k

的滑动窗口,它从数组的最左边移动到最右边。

你只能在窗口中看到 k

个数字。

每次滑动窗口向右移动一个位置。

以下是一个例子:

该数组为 [1 3 -1 -3 5 3 6 7],k为 3。
在这里插入图片描述

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
输入格式

输入包含两行。

第一行包含两个整数 n和 k,分别代表数组长度和滑动窗口的长度。

第二行有 n个整数,代表数组的具体数值。
同行数据之间用空格隔开。

输出格式

输出包含两个。

第一行输出,从左至右,每个位置滑动窗口中的最小值。

第二行输出,从左至右,每个位置滑动窗口中的最大值。

输入样例:

8 3
1 3 -1 -3 5 3 6 7

输出样例:

-1 -3 -3 -3 3 3
3 3 5 5 6 7

我们可以发现 如果我们要求最小值 举个例子 1 3 -1 最小值-1 然后窗口往前移动
3 -1 -3 我们可以发现 3 在-1前面 也是没啥用了

在这里插入图片描述
重点理解注释

#include <iostream>
using namespace std;
const int N=1000010;
int a[N];
int main(void)
{   int n,k;
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++)cin>>a[i];//存元素
    
    int q[N],hh=0,tt=-1;
    //维护单调递增 队头最小
    for(int i=0;i<n;i++)//滑入窗口的元素指针
    {
        if(hh<=tt&&q[hh]<i-k+1)hh++;//如果窗口划过队头 说明队头出队了
        //队头可能是上一个窗口中任意一个元素的下标! 不一定就是窗口中最左边的那个
        //自己考虑一下如果加入新元素 把队列所有元素都弹出的情况 这时候tt会
        //先越过hh 然后++tt 然后队列剩下一个新加入的元素 这时候tt hh位置一样他们存同一个下标
        //并且这个下标比窗口左端大呢
        while(hh<=tt&&a[q[tt]]>=a[i])tt--;//如果队不空 并且前面没用 弹出
        q[++tt]=i;//存下标 下标有序
        if(i-k+1>=0)printf("%d ",a[q[hh]]);//窗口长度够才输出
    }    
    puts("");//换行
    //清空队
    hh=0,tt=-1;
     //维护单调递减队头最大
    for(int i=0;i<n;i++)
    {
        if(hh<=tt&&q[hh]<i-k+1)hh++;
        //窗口每次只往后挪一格 
        //所以队列往前一格就行了 下一格的下标绝对在队列里 因为最坏队列存的也是
       //举个例子 [1 2 3]4 ->1[2 3 4] 窗口这么移动 队列弹去1还有2这是最坏的
       //如果不是这样 hh++ q[hh]可能像前面说的 可能指的别的位置了 是合法的(在新窗口)
       
        while(hh<=tt&&a[q[tt]]<=a[i])tt--;
        q[++tt]=i;
        if(i-k+1>=0)printf("%d ",a[q[hh]]);
        
    }
    return 0;
    
}

标记一下(讲解来源:董晓+yxc)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值