栈与队列

栈_概念

栈,可以理解为一个洗碗的流程,每次拿走的肯定是最上面的碗拿去洗,即先进后出;

而且必须是这个顺序,如果我要洗第7个碗,我就得把上面的1~6个碗全部拿走(甚至可以不洗),我才能拿走第7个碗;

STL

-------建立一个栈-------
//内部元素类型都为int;
stack<int>s;
//int stk[N] , sz = 0;

-------将x放到栈的顶部-------
//放入餐盘,每次放的都是最上面
s.push(x);
//stk[sz++] = a;

-------将栈顶元素删除-------
//从最上面拿掉一个盘子去洗
s.pop();
//sz--;

-------查询栈顶元素-------
//查看最上面的盘子是否干净
s.top();
//stk[sz];

-------查询元素个数-------
//还有多少个盘子没洗
s.size();
//sz;

-------查询栈是否为空-------
//洗完了吗,洗完了就回答是的(true),否则就是false
s.empty();
//if(sz > 0)
//{
//    return true;
//}
//else
//{
//    return false;
//}

STL确实方便,但是如果STL无法打开O2优化,就会有点慢;

所以如果要追求运行速度或者脑残面试官的情况下,往往建议手写栈;

例题_手写栈

https://www.acwing.com/problem/content/830/
实现一个栈,栈初始为空,支持四种操作:
push x – 向栈顶插入一个数 x;
pop – 从栈顶弹出一个数;
empty – 判断栈是否为空;
query – 查询栈顶元素。
现在要对栈进行 M 个操作,其中的每个操作 3 和操作 4 都要输出相应的结果。

题解

#include<bits/stdc++.h>
using namespace std;
int n;
int stk[100005];
int sz;
int main()
{
    cin >> n;
    while(n--)
    {
        string op;
        cin >> op;
        if(op == "push")
        {
            int x;
            cin >> x;
            stk[++sz] = x;
        }
        else if(op == "query")
        {
            cout << stk[sz] << endl;
        }
        else if(op == "pop")
        {
            sz--;
        }
        else if(op == "empty")
        {
            if(sz <= 0)
            {
                cout << "YES" << endl;
            }
            else
            {
                cout << "NO" << endl;
            }
        }
    }
    
    return 0;
}

例题_STL

https://www.acwing.com/problem/content/3305/

给定一个表达式,其中运算符仅包含 +,-,*,/(加 减 乘 整除),可能包含括号,请你求出表达式的最终值。

确定-不是负号,且一定合法;

思路

首先分析一下,四则运算如果没有括号怎么算;

比如:3+1+41+59,分别是3->1->41->59,即从左往右,也可以看成每次都是使用的最左边的数字,且必须从左往右读取;

这不就是栈吗;

所以,本题有两个核心关键点:

(1)双栈,一个操作数栈,一个运算符栈;

(2)运算符优先级,栈顶运算符,和,即将入栈的运算符的优先级比较:

如果栈顶的运算符优先级低,新运算符直接入栈

如果栈顶的运算符优先级高或者相同,先出栈计算,新运算符再入栈

举例:

还是3+1+41+59,最开始进入的是+,由于栈顶没有元素,算不算都可以;

接着还是+,此时栈顶运算符为+,同级运算符出栈计算,去掉1和3,得到4,入操作数栈,然后丢掉第一个+;

再是,此时栈顶运算符为第二个+,于是先计算41,得到另一个4,入操作数栈,此时操作数栈有两个4;

然后是+,栈顶是*,直接入运算符栈;

接着是,栈顶为+,先计算59,得到45,放入操作数栈,丢掉*;

最后将剩下的操作数栈和运算符栈相对比,最后只剩下第二个+和第三个+,结果为4+4+45 = 53;

题解

#include<bits/stdc++.h>
using namespace std;

stack<int> num;
stack<char> op;

//优先级表
unordered_map<char, int> h{ {'+', 1}, {'-', 1}, {'*',2}, {'/', 2} };


void eval()//求值
{
    int a = num.top();//第二个操作数
    num.pop();

    int b = num.top();//第一个操作数
    num.pop();

    char p = op.top();//运算符
    op.pop();

    int r = 0;//结果 

    //计算结果
    if (p == '+') r = b + a;
    if (p == '-') r = b - a;
    if (p == '*') r = b * a;
    if (p == '/') r = b / a;

    num.push(r);//结果入栈
}

int main()
{
    string s;//读入表达式
    cin >> s;

    for (int i = 0; i < s.size(); i++)
    {
        if (isdigit(s[i]))//数字入栈
        {
            int x = 0, j = i;//计算数字
            while (j < s.size() && isdigit(s[j]))
            {
                x = x * 10 + s[j] - '0';
                j++;
            }
            num.push(x);//数字入栈
            i = j - 1;
        }
        //左括号无优先级,直接入栈
        else if (s[i] == '(')//左括号入栈
        {
            op.push(s[i]);
        }
        //括号特殊,遇到左括号直接入栈,遇到右括号计算括号里面的
        else if (s[i] == ')')//右括号
        {
            while(op.top() != '(')//一直计算到左括号
                eval();
            op.pop();//左括号出栈
        }
        else
        {
            while (op.size() && h[op.top()] >= h[s[i]])//待入栈运算符优先级低,则先计算
                eval();
            op.push(s[i]);//操作符入栈
        }
    }
    while (op.size()) eval();//剩余的进行计算
    cout << num.top() << endl;//输出结果
    return 0;
}

队列_概念

如果说栈是洗碗,那么队列就是食堂打菜,先到先得,先进先出;

STL

queue< int > q          定义一个队列
q.empty()               如果队列为空返回true,否则返回false
q.size()                返回队列中元素的个数
q.pop()                 删除队列首元素但不返回其值
q.front()               返回队首元素的值,但不删除该元素
q.push()                在队尾压入新元素
q.back()                返回队列尾元素的值,但不删除该元素

判断队列是空还是满还是有点说法的:

栈空: 队首标志=队尾标志时,表示栈空。

栈满 : 队尾+1 = 队首时,表示栈满。

手写队列

-------定义一个队列-------
int team[N];
head = 0, tail = -1;//将尾部下标为-1;

-------队尾插入-------
team[ ++tail ] = x;

-------删除队列最前端-------
head++;

-------判断队列是否为空-------
if (head <= tail)
{
	return false;
}
else
{
	return true;
}

例题

https://www.acwing.com/problem/content/831/

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

push x – 向队尾插入一个数 x;
pop – 从队头弹出一个数;
empty – 判断队列是否为空;
query – 查询队头元素。
现在要对队列进行 M 个操作,其中的每个操作 3 和操作 4 都要输出相应的结果。

题解_手写队列

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

//[hh, tt] 之间为队列(左闭右闭)
int hh = 0;//队头位置
int tt = -1;//队尾位置
//操作次数
int m;
//操作方式
string s;

//入队:队尾先往后移动一格,再放入要插入的数据
void push(int x){
    q[++tt] = x;
}
//出队:队头往后移动一格
void pop(){
    hh++;
}
//[hh, tt]表示队列区间,当tt >= hh时,区间为空
void empty(){
    if(tt >= hh) cout << "NO" << endl;
    else cout << "YES" << endl;
} 
//hh指向队头,q[hh]代表队头元素
void query (){
    cout << q[hh] << endl;
}

int main(){
    cin >> m;
    while(m--){
        cin >> s;
        //入队
        if(s == "push"){
            int x;
            cin >> x;
            push(x);
        }
        //出队
        if(s == "pop"){
            pop();
        }
        //问空
        if(s == "empty"){
            empty();
        }
        //问队头
        if(s == "query"){
            query();
        }
    }
}

例题_单调栈

https://www.acwing.com/problem/content/832/

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

题解

暴力枚举(大概率TLE)

for (int i = 0; i < n; i++)
{
	for (int j = i - 1; j >= 0; j++)
	{
		if (a[i] > a[j])
		{
			cout << a[j];
			break;
		}
	}
}

单调栈解法

说不清楚,所以拿1,4,1,5举例

先创建一个栈s,再遍历,第一个是1,栈里元素为空,打印-1,将1放入栈顶;

第二个是4,栈顶元素为1,4比1大,打印1,4放入扎顶;

第三个是1,栈顶元素为4,1小于4,4出栈;

栈顶元素为1,1等于1,1出栈;

栈顶元素为空,打印-1,1放入栈顶;

第四个是5,栈顶元素为1,5大于1,打印1,5放入栈顶;

#include <iostream>
using namespace std;
const int N = 100010;
int stk[N], tt;

int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        int x;
        cin >> x;
        while (tt && stk[tt] >= x) 
           tt -- ;//如果栈顶元素大于当前待入栈元素,则出栈
        if (!tt) 
           printf("-1 ");//如果栈空,则没有比该元素小的值。
        else 
           printf("%d ", stk[tt]);//栈顶元素就是左侧第一个比它小的元素。
        stk[ ++ tt] = x;
    }
    return 0;
}

例题_单调队列 / 滑动窗口

https://www.acwing.com/problem/content/156/

给定一个大小为 n≤10^6 的数组。

有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边。

你只能在窗口中看到 k 个数字。

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

思路

例如在1 [3 -1 -3] 5 3 6 7中,窗口里,只要我-3在,你3和-1就永远不可能是最小值,就直接弹出。

最小值和最大值分开来做,两个for循环完全类似,都做以下四步:

1.解决队首已经出窗口的问题;
2.解决队尾与当前元素a[i]不满足单调性的问题;
3.将当前元素下标加入队尾;
4.如果满足条件则输出结果;

需要注意的细节:

1.上面四个步骤中一定要先3后4,因为有可能输出的正是新加入的那个元素;
2.队列中存的是原数组的下标,取值时要再套一层,a[q[]];
3.算最大值前注意将hh和tt重置;
4.此题用cout会超时,只能用printf;
5.hh从0开始,数组下标也要从0开始。

题解

#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int n, k, q[N], a[N];//q[N]存的是数组下标
int main()
{
    int tt = -1, hh=0;//hh队列头 tt队列尾
    cin>>n>>k;
    for(int i = 0; i <n; i ++)
        cin>>a[i];
    for(int i = 0; i < n; i ++)
    {
        //维持滑动窗口的大小
        //当队列不为空(hh <= tt) 且 当当前滑动窗口的大小(i - q[hh] + 1) 大于 我们设定的滑动窗口的大小(k),队列弹出队列头元素以维持滑动窗口的大小
        if(hh <= tt && k < i - q[hh] + 1) 
        hh ++;
        //构造单调递增队列
        //当队列不为空(hh <= tt) 且 当队列队尾元素>=当前元素(a[i])时,那么队尾元素
        //就一定不是当前窗口最小值,删去队尾元素,加入当前元素(q[ ++ tt] = i)
        while(hh <= tt && a[q[tt]] >= a[i]) 
        tt --;
        q[ ++ tt] = i;
        if(i + 1 >= k) 
        printf("%d ", a[q[hh]]);
    }
    puts("");
    hh = 0,tt = -1;
    for(int i = 0; i < n; i ++)
    {
        if(hh <= tt && k < i - q[hh] + 1) 
            hh ++;
        while(hh <= tt && a[q[tt]] <= a[i]) 
            tt --;
        q[ ++ tt] = i;
        if(i + 1 >= k ) 
            printf("%d ", a[q[hh]]);
    }
    return 0;
}
posted @ 2022-08-25 13:45  Zilliax  阅读(56)  评论(0)    收藏  举报