表达式的计算方式以及相互转换
首先介绍我们熟知的一种表达式:中缀表达式
中缀表达式就是我们日常生活中所能见到的一般表达式,例如:2+3*(4+5),根据运算符优先级,我们很容易知道先计算括号内的表达式,然后先乘除再加减,从左往右进行迭代计算最终获取正确结果,但对于计算机来说这并不容易,顺序执行时计算机不能预测我们的表达式后面部分的运算符是否会优先于当前,故我们利用计算机计算中缀表达式的值时需要合理进行编码已得到正确的结果。正因为中缀表达式不方便顺序进行运算,因此引入前缀表达式(波兰式)和后缀表达式(逆波兰式),前缀表达式即运算符位于操作数之前的表达式,例如:+ 2 * 3 + 4 5(等价于前面中缀表达式:2+3*(4+5))
后缀表达式即运算符在操作数之后,例如:2 3 4 5 + * +(等价于:2+3*(4+5))。
下面先给出中缀表达式转换至前、后缀表达式的方法,再依次给出计算前、后缀表达式的值的方法。
中缀转换为前缀:(通常来说都是转换成后缀表达式,不过这里也给出转换前缀的方法)
工具为两个栈:运算符栈S1和存储结果的栈S2,整个过程从后往前遍历中缀表达式,
1.当遇到操作数时,将其入栈S2;
2.遇到运算符时:若S1为空,或栈顶运算符为右括号时直接将此运算符入栈,否则比较其与S1栈顶运算符的优先级,若优先级高于或者等于栈顶运算符的优先级,则将本运算符入栈,
否则,将S1栈顶的运算符不断弹出并放入S2中,直至S1中栈顶运算符优先级满足上述条件再将运算符入栈;
3.如果遇到右括号,则直接压入S1,如果是左括号,则不断弹出S1栈顶运算符并放入S2中,直至遇到右括号为止(注意,括号并不会出现在转换后的表达式中,我们遇到右括号后会直接舍弃掉这两个括号)
4.重复上述过程直到遍历完整个表达式
5.将S1中剩余的运算符依次弹出并放进S2中,最后将S2中所有字符依次弹出得到的表达式就是前缀表达式。
中缀转换为后缀:
主要工具为栈S,S存放运算符:整个过程从前往后遍历中缀表达式:
1.当遇到操作数时,直接将其加入结果序列(vector或者string甚至于直接输出,看题目需求);
2.遇到左括号时将左括号入栈S;
3.遇到右括号时不断取出S栈顶元素并加入结果序列,直至栈顶为左括号(我们同样要舍弃掉这对括号,直接将左括号出栈即可);
4.遇到运算符时,若栈空直接入栈,否则将其于S栈顶运算符比较,若优先级高于栈顶运算符(注意,不能等于)时直接入栈S,否则不断取出栈顶运算符加入结果序列,直至栈顶运算符优先级低于当前字符或栈空(乘=除>加=减>左括号);
5.遍历结束后依次弹出S中所有剩余运算符并加入结果序列。
下面给出两种表达式的计算方法:
前缀表达式计算方法:
工具为一个栈,从后往前遍历表达式,若为操作数直接入栈,若为运算符从栈中取出两个元素进行相应运算,并将运算结果再次入栈,遍历完整个表达式后栈中会剩下一个元素,也即是解;通常来说,处理前缀表达式比较困难的部分是对操作数的判断,以下给出PTA中一道例题,仅供参考;
题面:(题目链接:https://pintia.cn/problem-sets/988034414048743424/problems/988039125346848768)
算术表达式有前缀表示法、中缀表示法和后缀表示法等形式。前缀表达式指二元运算符位于两个运算数之前,例如2+3*(7-4)+8/4的前缀表达式是:+ + 2 * 3 - 7 4 / 8 4。请设计程序计算前缀表达式的结果值。
输入格式:
输入在一行内给出不超过30个字符的前缀表达式,只包含+、-、*、/以及运算数,不同对象(运算数、运算符号)之间以空格分隔。
输出格式:
输出前缀表达式的运算结果,保留小数点后1位,或错误信息ERROR。
输入样例:
+ + 2 * 3 - 7 4 / 8 4
输出样例:
13.0
AC代码:
#include <bits/stdc++.h>
using namespace std;
#define reset(x) memset(x, 0, sizeof(x))
#define Q_in_out \
ios::sync_with_stdio(false); \
cin.tie(0); \
cout.tie(0);
typedef long long int ll;
typedef long double ld;
typedef pair<int, int> P;
#define forl(l,r) for(int i=l;i<r;i++)
#define forr(r,l) for(int i=r;i>=l;i--)
const int N = 1e6+5;
const int modp = 1e9+7;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const double pi = acos(-1.0);
const double e = 2.718281828459045;
double change(string s){
int i=0;
double f=1.0;
if(s[i]=='-'||s[i]=='+') {
if(s[i]=='-')
f*=-1;
i++;
}
int tmp=0,fg=0,cnt=0;
for(;i<s.size();i++){
if(s[i]=='.'){
fg=1;
continue;
}
if(fg){
cnt++;
}
tmp+=s[i]-'0';
tmp*=10;
}
tmp/=10;
return f*tmp/pow(10,cnt);
}
int main(){
stack<double>s;
string c[35];
int cnt=0;
string t;
while (cin>>t)
c[cnt++]=t;
int f=0;
for(int i=cnt-1;i>=0;i--){
if(c[i]=="+"||c[i]=="-"||c[i]=="*"||c[i]=="/"){
double t1,t2;
if(s.size()){
t1=s.top();
s.pop();
}
else{
f=1;break;
}
if(s.size()){
t2=s.top();
s.pop();
}
else{
f=1;break;
}
if(c[i]=="*") s.push(t1*t2);
if(c[i]=="+") s.push(t1+t2);
if(c[i]=="-") s.push(t1-t2);
if(c[i]=="/") {
if(t2==0){
f=1;
break;
}
s.push(t1/t2);
}
}
else{
// s.push(atof(c[i].c_str()));
s.push(change(c[i])); //可以使用库函数atof,也可以自己手写一个函数来转换
}
}
if(f) printf("ERROR\n");
else
printf("%.1f\n",s.top());
return 0;
}
后缀表达式的计算方法:
工具为一个栈,整个过程从前往后遍历表达式,若遇到操作数直接入栈,否则从栈中取出两个元素运算后将结果入栈,遍历结束后栈中只剩一个元素即为结果
(与前缀表达式求解类似,代码实现简单,注意处理操作数即可,此处不给出例题以及代码了)