OI学习笔记10:后缀表达式
后缀表达式
一、定义
1、中缀表达式。
- 中缀表达式是人类善于、也是最常用的一种表达式形式,通常被描述为 \(A\) \(op\) \(B\),其中 \(op\) 为运算符。
- 例如:\(1-(3+2)*2\) 就是一种较复杂的中缀表达式。
2、后缀表达式。
- 后缀表达式是计算机最能理解的表达式形式。由于计算机的堆栈结构,使得其能够快速地求解后缀表达式。
- 后缀表达式被描述为 \(A\) \(B\) \(op\),其中 \(op\) 为运算符。
- 例如:\(1\ 2 + 3\ *\) 就是一种较为简单的后缀表达式。
- 更学术地,我们有关于后缀表达式的如下定义:
- 如果 \(E\) 是一个操作数,则 \(E\) 的后缀表达式是其本身。
- 如果 \(E\) 是 \(E_1\ op\ E_2\) 形式的表达式,其中 \(op\) 是任何二元操作符,且优先级不高于 \(E_1\)、\(E_2\) 中括号外的操作符,则 \(E\) 的后缀表达式为 \(E'_1\ E'_2\ op\),其中 \(E'_1\)、\(E'_2\) 分别是 \(E_1\)、\(E_2\) 的后缀表达式。
- 如果 \(E\) 是 \(E_1\) 形式的表达式,则 \(E_1\) 的后缀表达式就是 \(E\) 的后缀表达式。
二、后缀表达式相关算法
1、后缀表达式求值。
(1)算法流程。
- 给定后缀表达式,求其值。我们可以用栈来 \(O(N)\) 地求出值。
- 建立一个用于存数的栈,逐一扫描该后缀表达式中的元素。
- 如果遇到一个数,则把该数入栈。
- 如果遇到运算符,就取出栈顶的两个数进行计算,把结果入栈。
- 扫描完成后,栈中恰好剩下一个数,就是该后缀表达式的值。
(2)代码实现。
int getValue(string s){//s为后缀表达式
stack<int> st;//存数字的栈
for(int i = 0; i < s.length(); i++){
if(isdigit(s[i])) st.push(s[i] - '0');
else{
int n2 = st.top(); st.pop();//弹数字
int n1 = st.top(); st.pop();
//计算
if(s[i] == '+') st.push(n1 + n2);
if(s[i] == '-') st.push(n1 - n2);
if(s[i] == '*') st.push(n1 * n2);
if(s[i] == '/') st.push(n1 / n2);
if(s[i] == '^') st.push(pow(n1, n2));
}
}
return st.top();
}
2、中缀表达式转后缀表达式。
(1)算法流程。
- 建立一个用于存运算符的栈,逐一扫描该中缀表达式中的元素。
1.如果遇到一个数,输出该数。
2.如果遇到左括号,把左括号入栈。
3.如果遇到右括号,不断取出栈顶并输出,直到栈顶为左括号,然后将左括号出栈。
4.如果遇到运算符,只要栈顶符号的优先级不低于新符号,就不断取出栈顶并输出,最后把新符号入栈。优先级为乘除\(>\)加减\(>\)左括号。 - 依次取出并输出栈中所有的剩余符号,最终输出的序列就是一个与原中缀表达式等价的后缀表达式。
(2)代码实现。
int lev(char c){//返回符号的优先级,此函数可以使用 map<char. int> 代替
if(c == '+' || c == '-') return 1;
if(c == '*' || c == '/') return 2;
if(c == '^') return 3;
if(c == ')' || c == '(') return 0;
return -1;
}
string toSuffix(const string &s){//转换
string ret = "";
stack<char> st;
for(int i = 0; i < s.length(); i++){
if(isdigit(s[i])) ret += s[i];
else if(s[i] == '(') st.push(s[i]);
else if(s[i] == ')'){
while(st.top() != '(')
ret += st.top(), st.pop();
st.pop();
}
else{
while(!st.empty() && lev(st.top()) >= lev(s[i]))
ret += st.top(), st.pop();
st.push(s[i]);
}
}
while(!st.empty())
ret += st.top(), st.pop();
return ret;
}
三、例题。
P1175 表达式的转换
平常我们书写的表达式称为中缀表达式,因为它将运算符放在两个操作数中间,许多情况下为了确定运算顺序,括号是不可少的,而中缀表达式就不必用括号了。
后缀标记法:书写表达式时采用运算紧跟在两个操作数之后,从而实现了无括号处理和优先级处理,使计算机的处理规则简化为:从左到右顺序完成计算,并用结果取而代之。
例如:8-(3+2*6)/5+4
可以写为:8 3 2 6*+5/-4+
。
其计算步骤为:8 3 2 6 * + 5 / – 4 + 8 3 12 + 5 / – 4 + 8 15 5 / – 4 + 8 3 – 4 + 5 4 + 9
编写一个程序,完成这个转换,要求输出的每一个数据间都留一个空格。
一道中缀表达式转后缀表达式加后缀表达式计算的板子题,具体在代码中体会:
#include<cstdio>
#include<bits/stdc++.h>
#include<list>
using namespace std;
inline int lev(char c){
if(c == '+' || c == '-') return 1;
if(c == '*' || c == '/') return 2;
if(c == '^') return 3;
if(c == ')' || c == '(') return 0;
return -1;
}
inline int power(int a, int b){
int ans = 1;
while(b){
if(b & 1) ans *= a;
a *= a;
b >>= 1;
}
return ans;
}
inline string toSuffix(const string &s){//中缀表达式转后缀表达式
string ret = "";
stack<char> st;
for(int i = 0; i < s.length(); i++){
if(isdigit(s[i])) ret += s[i];
else if(s[i] == '(') st.push(s[i]);
else if(s[i] == ')'){
while(st.top() != '(')
ret += st.top(), st.pop();
st.pop();
}
else{
while(!st.empty() && lev(st.top()) >= lev(s[i]))
ret += st.top(), st.pop();
st.push(s[i]);
}
}
while(!st.empty())
ret += st.top(), st.pop();
return ret;
}
inline int calcNum(int n1, int n2, char s){
if(s == '+') return n1 + n2;
else if(s == '-') return n1 - n2;
else if(s == '*') return n1 * n2;
else if(s == '/') return n1 / n2;
else if(s == '^') return power(n1, n2);
}
inline void calc(string s){//计算并输出过程
int tail = s.length();
list<int> st;
for(int i = 0; i < tail; i++){
printf("%c ", s[i]);
}
printf("\n");
for(int i = 0; i < tail; i++){
if(isdigit(s[i])){
st.push_back(s[i] - '0');
}
else{//这里计算使用更为方便的list,而不是使用stack,使用stack不便于输出过程。
int n2 = st.back(); st.pop_back();
int n1 = st.back(); st.pop_back();
st.push_back(calcNum(n1, n2, s[i]));
for(list<int>::iterator it = st.begin(); it != st.end(); it++)
printf("%d ", *it);//输出迭代器。
for(int j = i + 1; j < tail; j++)
printf("%c ", s[j]);
printf("\n");
}
}
}
int main(){
string b;
cin>>b;
calc(toSuffix(b));
return 0;
}