栈与队列
栈_概念
栈,可以理解为一个洗碗的流程,每次拿走的肯定是最上面的碗拿去洗,即先进后出;
而且必须是这个顺序,如果我要洗第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;
}

浙公网安备 33010602011771号