备战NOI 数据结构——栈与单调栈(stack) 以及后缀表达式


今天我们来学习数据结构中最简单的——栈

引入

一只NOI选手csk因在NOI赛场上忘开 l o n g l o n g longlong longlong,本来打出了正解的题目只拿了 30 p t s 30pts 30pts,结果离金牌只差了 10 p t s 10pts 10pts,从而没有与心念的清北签约,加上高考的失利,走投无路的他来到食堂打工 (十年OI一场空,不开longlong见祖宗)

他当上了一名洗碗工,每天都要洗盘子

第一个人吃完饭后,送来了 1 , 2 , 3 1,2,3 1,2,3号盘子,csk将这些盘子依次摆放,很快他就先后洗完了最上面的第 3 , 2 3,2 3,2号盘子,随后,又一位客人过来,放上了第 4 , 5 4,5 4,5号盘子,csk继续从上往下洗盘子,很快就洗完了

栈
这种后进先出的数据结构,就是栈

栈的概念

栈是一种后进先出的数据结构,支持 入栈(push),出栈(pop)和访问栈顶(top) 三个操作,新入栈的元素会被后入栈的元素压到下面。

在这里插入图片描述

代码实现

为了方便理解,代码以结构体和函数的形式

定义和初始化(init)

我们只需用一个数组和一个变量即可实现栈

struct stack{
	int s[MAX];//存放栈 
	int top;//指向栈顶的下标,初始为0
};

入栈(push)

将该元素 a a a放入栈顶,即将其放到数组的最后一个位置

void push(int a){
	s[++top] = a;
} 

出栈(pop)

将栈顶元素弹出,仅需将数组最后一个元素的指针 − 1 -1 1即可

void pop(){
	if(top)//判断栈是否为空
		top--;
}

访问栈顶元素(query)

直接返回数组的最后一个元素即可,栈为空返回-1

int query(){
	if(top)
		return s[top];
	return -1;
}

查询栈的元素个数(size)

其实元素个数就是top的值

int size(){
	return top;
}

判断是否为空(empty)

栈为空,返回1,否则返回0

bool empty(){
	if(top)
		return true;
	return false;
}

当然也可写作

bool empty(){
	return top == 0;
}

清空栈(clear)

将数组最后元素的指针改为0即可

void clear(){
	top = 0;
}

完整模板

将以上和在一起即可

struct stack{
	int s[MAX];//存放栈 
	int top;//指向栈顶的下标
	void push(int a){
	//入栈
		s[++top] = a;
	}
	void pop(){
	//出栈
		if(top)
			top--;
	}
	int query(){
	//访问栈顶
		return s[top];
	}
	int size(){
	//返回栈元素个数
		return top;
	}
	bool empty(){
	//判断栈是否为空
		return top == 0;
	}
	void clear(){
	//清空栈
		top = 0;
	}
};

练习题

请你实现一个栈(stack),支持如下操作:

  • push(x):向栈中加入一个数 x x x
  • pop():将栈顶弹出。如果此时栈为空则不进行弹出操作,输出 Empty
  • query():输出栈顶元素,如果此时栈为空则输出 Anguei!
  • size():输出此时栈内元素个数。

STL的栈

STL也为我们提供了栈
头文件 stack

#include <stack>

定义

stack <变量类型> 栈名;
stack <int> s;//定义一个名为s,int类型的栈

常见操作

支持的操作类似模板

stack <int> s; int x;
s.push(x);//在栈顶压入变量x
s.pop();//栈顶出栈
s.top();//返回栈顶
s.size();//返回栈元素个数
s.empty();//判断栈是否为空
s.clear();//清空栈

用STL的栈完成例题

#include<bits/stdc++.h>
using namespace std;
int main(){
	ios::sync_with_stdio(false);
    int T;
    cin >> T;
    while(T--){
    	int n;
    	cin >> n;
    	stack <unsigned long long> a;
    	string b;
    	for(int i=1;i<=n;i++){
    		cin >> b;
    		if(b == "push"){
    			unsigned long long c;
    			cin >> c;
    			a.push(c);
			}
			else if(b == "pop"){
				if(a.empty()) cout << "Empty\n";
				else a.pop();
			}
			else if(b == "query"){
				if(a.empty()) cout << "Anguei!\n";
				else cout << a.top() << "\n";
			}
			else cout << a.size() << "\n";
		}
	}
    return 0;
}

表达式计算

表达式的计算是栈的一大用处。
对于算术表达式,有前缀表达式,中缀表达式,后缀表达式(注: o p t opt opt为运算符, x , y x,y x,y表示数字)
中缀表达式: 就是我们数学中最常使用的一种表达式,例如 1 + ( 2 − 3 ) 1+(2-3) 1+(23)
前缀表达式: 又称波兰式,就是将运算符放到数字前面,形如 o p t   x   y opt \ x \ y opt x y 表示 x   o p t   y x \ opt \ y x opt y,例如 +   1 − 2    3 +\ 1-2 \ \ 3 + 12  3
后缀表达式: 又称逆波兰式,就是将运算符放到数字后面,形如 x   y   o p t x \ y \ opt x y opt 表示 x   o p t   y x \ opt \ y x opt y,例如 2    3 − 1   + 2 \ \ 3 - 1 \ + 2  31 +
对于计算机而言,我们最常使用的是后缀表达式,可以用栈 O ( l e n ) O(len) O(len)(len表示表达式长度)求出它的值

后缀表达式求值

如何求后缀表达式的值?

  • 先建立一个栈,并扫描一遍表达式
    -(1)如果遇到一个数,就把它入栈
    -(2)如果遇到一个运算符,就取出栈顶的两个元素进行计算,再把结果入栈
  • 最后栈中只剩一个数,这个数就是表达式的值

例题(洛谷P1449)

所谓后缀表达式是指这样的一个表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右新进行(不用考虑运算符的优先级)。

如: 3*(5-2)+7 \texttt{3*(5-2)+7} 3*(5-2)+7 对应的后缀表达式为: 3.5.2.-*7.+@ \texttt{3.5.2.-*7.+@} 3.5.2.-*7.+@。在该式中,@ 为表达式的结束符号。. 为操作数的结束符号。

long long now=0;
while(cin >> ch ){
	if(ch=='@')
		break;
	if(ch>='0' && ch<='9'){
		now=now*10+ch-'0';
		continue;
	}
	else if(ch=='.'){
		sta[++top]=now;
		now=0;
		continue;
	}
	else{
		if(ch=='+'){
			sta[top-1]+=sta[top];
			--top;
		}
		else if(ch=='-'){
			sta[top-1]-=sta[top];
			--top;
		}
		else if(ch=='*'){
			sta[top-1]*=sta[top];
			--top;
		}
		else if(ch=='/'){
			sta[top-1]/=sta[top];
			--top;
		}
	}
}
cout<<sta[1];

中缀表达式转成后缀表达式

中缀表达式转成后缀表达式

  • 先建立一个栈用来存储运算符,扫描一遍表达式的元素
    -(1)如果遇到一个数,直接输出这个数
    -(2)如果遇到左括号,把左括号入栈
    -(3)如果遇到右括号,不断取出栈顶并输出,直到栈顶为左括号,再把左括号出栈
    -(4)如果遇到运算符,只要栈顶运算符的优先级不低于新运算符,就不断输出并弹出栈顶,最后把新符号入栈
  • 最后依次输出并弹出栈顶,输出的序列就是一个与原中缀表达式等价的后缀表达式

代码实现, n > 0 n>0 n>0

char ch;//中缀表达式 
unsigned long long num = 0;
map <char,int> mp;//存储运算符优先级 
mp['+'] = 1; mp['-'] = 1; mp['*'] = 2; mp['/'] = 2;
//有需要自己写 
stack <char> s;
while(cin >> ch){
   	if(ch >= '0' && ch <= '9')
   		num = num*10+ch-'0';
	else{
		if(num){
			cout << num << " ";
			num = 0;
		}
		if(ch == '(')
			s.push(ch);
		else if(ch == ')'){
			while(s.top() != '('){
				cout << s.top() <<" ";
				s.pop();
			}
			s.pop();
		}
		else{
			while(!s.empty()&&mp[s.top()]>=mp[ch]){
				cout<< s.top() << " ";
				s.pop();
			}
			s.push(ch);
		}
	}
}
if(num > 0) cout << num << " ";
while(!s.empty()){
	cout << s.top() << " ";
	s.pop();
}

括号序列

栈也经常用来匹配括号序列

这里是引用对于括号序列,我们可以用类似于中缀表达式的括号处理,遇到左括号入栈,遇到右括号判断栈顶是不是不这个右括号匹配,是就出栈,不是则不合法,读者可以自己模拟一下这个过程

string str;
cin >> str;
stack <char> s
for(int i=0;i<str.size();i++){
	if(str[i] == '(' || str[i] == '[' || str[i] == '{'){
		s.push(str[i]);
	else{
		if(str[i] == ')' && s.top() == '('){
			s.pop(); continue;
		}
		if(str[i] == ']' && s.top() == '['){
			s.pop(); continue;
		}
		if(str[i] == '}' && s.top() == '{'){
			s.pop(); continue;
		}
		cout << "no";
		return 0;
	}
}
if(s.empty() cout << "yes";
else cout << "no";

单调栈

顾名思义,就是栈内元素具有单调性的一种栈
单调栈一般用来维护最大或最小值
现以单调递减栈为例,模拟一遍单调栈的过程

  • 建立一个栈,扫描一遍待操作元素
    -(1)如果栈内没有元素,直接入栈
    -(2)如果栈顶元素比新元素大,否则持续弹出栈顶元素,直到栈顶元素比新元素大或栈为空为止,此时被弹出元素之后第一个比它大的元素即为这个新元素,最后记得把新元素入栈
    -(3)栈底的元素即为被扫过元素的最大值
stack <int> s;
//a为待操作元素
for(int i=1;i<=n;i++){
	while(!s.empty() && a[i] > a[s.top()])
		s.pop();
	s.push(i);
}

经典例题——Largest Rectangle in a Histogram

如图所示,在一条水平线上有 n n n 个宽为 1 1 1 的矩形,求包含于这些矩形的最大子矩形面积(图中的阴影部分的面积即所求答案)。
在这里插入图片描述
对于这一道题,我们发现:

如果一个矩形的高度低于上一个矩形的高度,那么上一个矩形比它高的部分在之后将毫无用处

则这些没用的部分我们就可以删掉
所以,我们可以建立一个单调栈维护矩形高度,每弹出一个矩形,就把该高度矩形宽度 + 1 +1 +1,并用宽度乘高度来更新答案

#include <iostream>
#include <cstring>
#define ull unsigned long long
using namespace std;

const int MAX = 1e5+10;
int top;
long long w[MAX],s[MAX],ans;

int main(){
	ios::sync_with_stdio(false);
	int n;
	while(cin >> n){
		top = 0; ans = 0;
		memset(w,0,sizeof(w));
		if(n == 0) break;
		for(int i=1;i<=n+1;i++){
			long long h; 
			if(i == n+1) h = 0;
			else cin >> h;
			if(h > s[top] || !top){
				s[++top] = h;
				w[top] = 1;
			}
			else{
				long long width = 0;
				while(top && h <= s[top]){
					width += w[top];
					ans = max(ans,width*s[top]);
					top--;
				}
				s[++top] = h; w[top] = width+1;
			}
		}
		cout << ans << "\n";
	}
    return 0;
}

练习题

请自行完成以下练习

后缀表达式

表达式的值(提示:动态规划)

括号序列

单调栈

总结

栈(stack)作为大多数人学的第一个算法 (也是最简单的一个) 它的应用十分广泛,最主要就是用于表达式的计算 (其它好像没什么用)
本文字数5000,用时约6小时,求个三连不过分吧

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值