栈的应用【实验题】
使用栈实现后缀表达式计算,其中,在后缀表达式中,输入的数字为整数,且为正数,数字、符号之间用空格隔开,整个后缀表达式用“#”表示结束。其中,整个后缀表达式长度不超过200,每个数字位数不超过10。
提示:读取数据的过程中,可以利用栈处理每个数字。
输入样例:
11 2 3 + * #(注:对应的中缀表达式是11*(2+3))
6 2 3 + * 5 / 7 - #(注:对应的中缀表达式是6*(2+3)/5-7)
输出样例:
55
-1
#include<iostream>
#include<string>
using namespace std;
struct stack
{
int a[300];
int index = -1;
void instack(int x) //入栈操作
{
a[++index] = x;
}
void calculate(char k) //k是运算符
{
int sum = 0;
int k1, k2;
k1 = showtop(); outstack();
k2 = showtop(); outstack();
if (k == '+')
{
sum = k1 + k2;
}
else if (k == '-')
{
sum = k2 - k1;
}
else if (k == '*')
{
sum = k1 * k2;
}
else if (k == '/')
{
sum = k2 / k1;
}
instack(sum);
}
void outstack() //出栈
{
index--;
}
int showtop() //显示栈顶字符
{
return a[index];
}
};
int main()
{
stack f;
string b;
while (cin >> b && b != "#")
{
if (b == "+" || b == "-" || b == "*" || b == "/")
{
f.calculate(b[0]);
}
else
{
int num = stoi(b);
f.instack(num);
}
}
cout << f.showtop() << endl;
return 0;
}
整体还是很简单的,主要操作步骤如下
1.写一个栈的结构,这里面设置栈结构(a[]数组),index索引指向目前栈顶,并包含入栈、出栈、显示栈顶字符操作
2.输入字符
3.写运算calculate函数
要注意的问题是cin可以隔断输入的string b,此时比如单个输入的"11"就是要给字符串b
string b;
while (cin >> b && b != "#")
{
if (b == "+" || b == "-" || b == "*" || b == "/")
{
f.calculate(b[0]);
}
else
{
int num = stoi(b);
f.instack(num);
}
}
同时calculate()也可以用switch写,更简洁一些
void calculate(char k) {
int right = a[index--]; // 右操作数(栈顶)
int left = a[index--]; // 左操作数(次栈顶)
int sum = 0;
switch(k) {
case '+': sum = left + right; break;
case '-': sum = left - right; break;
case '*': sum = left * right; break;
case '/': sum = left / right; break; // 修正为 left / right
}
a[++index] = sum; // 结果入栈
}
单调栈
这是种特殊的栈结构,其元素按照单调递增或单调递减的顺序排列
普通暴力方法是 O(n²) ,这个可以优化至 O(n)。
典型例题:
题目描述
给出项数为 n 的整数数列 a1…n。
定义函数 f(i) 代表数列中第 i 个元素之后第一个大于 ai 的元素的下标,即 f(i)=mini<j≤n,aj>ai{j}。若不存在,则 f(i)=0。
试求出 f(1…n)。
输入格式
第一行一个正整数 n。
第二行 n 个正整数 a1…n。
输出格式
一行 n 个整数表示 f(1),f(2),…,f(n) 的值。
输入输出样例
输入
5 1 4 2 3 5
输出
2 5 4 5 0
说明/提示
【数据规模与约定】
对于 30% 的数据,n≤100;
对于 60% 的数据,n≤5×103 ;
对于 100% 的数据,1≤n≤3×106,1≤ai≤109。
整体做法就是,用栈来存储索引值,先从前往后遍历数列,遇到第 i 个元素 x ,让它和栈顶元素比较
- 如果栈顶元素小于它,就把栈顶元素踢出去,索引值 i 成为栈顶,此时栈顶索引对应的那个第一个比它大的索引就是 i 。
- 如果栈顶元素大于等于它,保存原来栈顶那个元素,把 i 直接加进来。
这样就形成了一个单调递减的栈,遍历每一个元素,后面的元素如果比它前面那个小就会进去待命,反正前面那个大数的结果不是后面那个小数,它只会在遇到比它大的数的时候才会找到答案,此时前面那堆小的答案也是那个新遇到的大数,所以就叽里咕噜全跑出栈来了。
还有一个比较形象的例子,这个题就好像是很多人同时向右看,求看到第一个比自己高的人的那个位置
为什么可以将复杂度降到 O(n)?
我想这是一个一边遍历数组一边比大小的过程,因为在遍历数组的同时本身就形成了一个递减的顺序,所以能够同时遍历数组 + 比大小,高
#include<iostream>
using namespace std;
int res[3000000]; //盛放答案 res[1]的值是第一个大于首元素的元素索引
int st[3000000]; //盛放索引,索引对应的值从大到小排列
int num[3000000]; //盛放原先的数列
int p = 0; //指针,首元素是1
int main()
{
int n,x;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> x;
num[i] = x;
}
for (int i = 1; i <= n; i++)
{
while (p && num[i] > num[st[p]])
//num[st[p]]:表示当前st栈中栈顶元素(索引)对应的那个数值
{
int j = st[p]; //设一个整数j存索引值
p--;
res[j] = i;//该索引值第一个比它大的索引是i
}
p++;
st[p] = i; //不管num[i]是大是小都要入栈
}
for (int i = 1; i <= n; i++)
cout << res[i] << " ";
return 0;
}
出栈顺序判断
题目描述
给出两个序列 pushed 和 poped 两个序列,其取值从 1 到 n(n≤100000)。已知入栈序列是 pushed,如果出栈序列有可能是 poped,则输出 Yes
,否则输出 No
。为了防止骗分,每个测试点有多组数据,不超过 5 组。
输入格式
第一行一个整数 q,询问次数。
接下来 q 个询问,对于每个询问:
第一行一个整数 n 表示序列长度;
第二行 n 个整数表示入栈序列;
第三行 n 个整数表示出栈序列;
输出格式
对于每个询问输出答案。
输入输出样例
输入
2 5 1 2 3 4 5 5 4 3 2 1 4 1 2 3 4 2 4 1 3
输出
Yes No
其实是一个模拟出栈过程的题
#include<iostream>
#include<string.h>
using namespace std;
int push[100000];
int pop[100000];
int q, n;
struct stack
{
int st[100000];
int ttop = -1; //top指向当前栈顶元素
void popp() { ttop--; }
void pushh(int x) { ttop++; st[ttop] = x; }
bool isempty() { return ttop == -1 ? 1 : 0; }
int top() { return st[ttop]; }
};
bool check() //模拟出栈过程
{
int cnt = 0; //表示pop[]的索引
stack s;
for (int i = 0; i < n; i++)
{
s.pushh(push[i]); //入栈
while (s.top() == pop[cnt]) //当栈顶元素等于pop[cnt]的时候出栈
{
cnt++; //pop[]顺便往后移
s.popp(); //出栈
if (s.isempty())
//如果出完栈以后栈空了,就直接退出,因为原来那个s.top()不会判断栈有没有空,会出现错误
{
break;
}
}
}
if (s.isempty()) return true;
else return false;
while (!s.isempty()) //清空栈
{
s.popp();
}
}
int main()
{
for (cin>>q; q; q--)
{
cin >> n;
for (int i = 0; i < n; i++) cin >> push[i];
for (int i = 0; i < n; i++) cin >> pop[i];
if (check()) cout << "Yes" << endl;
else cout << "No" << endl;
}
return 0;
}
队列
滑动窗口
用一个长度为k的窗口在长度为n的整数数列上从左往右滑动(其中k<n),每次滑动一个单位,求出每次滑动后每个窗口里面所包含的数的最大值。例如:当数列为[1, 3, -1, -3, 5, 3, 6, 7],窗口大小k=3,可以得出,
窗口位置 | 窗口内最大值 |
[1 3 -1] -3 5 3 6 7 | 3 |
1 [3 -1 -3] 5 3 6 7 | 3 |
1 3 [-1 -3 5] 3 6 7 | 5 |
1 3 -1 [-3 5 3] 6 7 | 5 |
1 3 -1 -3 [5 3 6] 7 | 6 |
1 3 -1 -3 5 [3 6 7] | 7 |
输入数据格式:
第一行,一个整数,表示测试样例的数量,例如3表示有3组测试样例
往后每两行对应一组测试样例,其中
第1行,两个整数,分别表示整数数列的长度n和窗口大小k
第2行,n个整数,表示一个整数数列
样例输入:
3(共3组测试样例)
8 3(第1组测试样例的n和k)
1 3 -1 -3 5 3 6 7(第1组测试样例的整数数列)
9 4(第2组测试样例的n和k)
1 2 3 4 5 6 7 8 9(第2组测试样例的整数数列)
8 3(第3组测试样例的n和k)
8 7 6 5 4 3 2 1(第3组测试样例的整数数列)
输出样例:
3 3 5 5 6 7(第1组测试样例的输出结果)
4 5 6 7 8 9(第2组测试样例的输出结果)
8 7 6 5 4 3(第2组测试样例的输出结果)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
using namespace std;
int nums[200];
//定义测试样例个数,数列长度,窗口大小
int n = 0, k = 0;
int result[100000];
int cnt = 0;
struct deque
{
int front;
int rear;
int queue[200];
deque(int x)
{
front = 0;
rear = 0;
for (int i = 0; i < 200; i++)
{
queue[i] = 0;
}
}
bool empty() //是否为空
{
return front == rear;
}
void push_back(int x) //后端入列
{
queue[rear] = x;
rear++;
}
void pop_back() //后端出列
{
//queue[rear] = 0;
rear--;
}
void pop_front() //前端出列
{
//queue[front] = 0;
front++;
}
int frontt() //返回队首元素
{
return queue[front];
}
int rearr() //返回队尾元素
{
return queue[rear];
}
void print()
{
for (int i = front; i < rear; i++)
{
cout << queue[i] << " ";
}
cout << endl;
}
};
void check(int k,int* nums)
{
deque dq(0);
deque result(0);
for (int i = 0; i < n; i++)
{
while (!dq.empty()&&dq.frontt()<= i-k-1)
{
dq.pop_front();
}
while (!dq.empty() && nums[i] >= nums[dq.rearr()])
{
dq.pop_back();
}
dq.push_back(i);
if (i >= k - 1)
{
result.push_back(nums[dq.frontt()]);
}
}
result.print();
}
int main()
{
//输入重定向
//FILE* instream;
//instream = freopen("input.txt", "r", stdin);
//int count = 0;
//if (scanf("%d", &count) != 1) //输入测试样例组数
// return 1;
int count;
cin >> count;
for (int i = 0; i < count; i++) //测试count组测试样例
{
cin >> n >> k;
for (int i = 0; i < n; i++)
{
cin >> nums[i];
}
check(k, nums);
}
//fclose(instream);
return 0;
}
用双端队列 qu 存储 nums[ ] 中的索引,用 result[ ]存储结果
遍历nums[ ]中所有元素
{
1. 移除超出窗口范围的元素(条件:当 qu 队首的元素小于窗口范围的时候)
2. 引进新元素,维持单调队列
如果这个新元素在 nums 中大于 qu 队尾的元素的时候,把 qu 队尾的元素踢出去
不管新元素是大是小都把它添加进队列中
3.当窗口形成的时候开始把结果记录进 result 中(条件:当前遍历的元素 i > k-1 时)
}
STL 双端队列的库函数
需要包含头文件 <deque>
声明: deque<int> dq
deque() | 默认构造函数,创建一个空的 deque 容器。 |
front() | 返回第一个元素的引用。 |
back() | 返回最后一个元素的引用。 |
begin() | 返回指向第一个元素的迭代器。 |
end() | 返回指向末尾元素后一位置的迭代器。 |
rbegin() | 返回指向最后一个元素的逆向迭代器。 |
rend() | 返回指向第一个元素之前位置的逆向迭代器。 |
empty() | 检查容器是否为空。 |
size() | 返回容器中的元素个数。 |
max_size() | 返回容器可容纳的最大元素个数。 |
clear() | 清除容器中的所有元素。 |
insert(iterator pos, const T& value) | 在 pos 位置插入 value 元素。 |
erase(iterator pos) | 移除 pos 位置的元素。 |
push_back(const T& value) | 在容器末尾添加 value 元素。 |
pop_back() | 移除容器末尾的元素。 |
push_front(const T& value) | 在容器前端添加 value 元素。 |
pop_front() | 移除容器前端的元素。 |
resize(size_type count) | 调整容器大小为 count ,多出部分用默认值填充。 |
swap(deque& other) | 交换两个 deque 容器的内容。 |
get_allocator() | 返回一个用于构造双端队列的分配器对象的副本。 |
队列易犯的错误:
1.手写队列,在返回队尾元素的时候注意是 return queue[ rear-1 ] ,因为 rear 是指向队尾元素的下一个空元素的