一.四则运算表达式
我们最常见的运算表达式是 : 12 * (3 + 4) - 6 + 8 / 2 这种类型,这就被称为是中缀表达式。
我们要用计算机来计算这一串表达式,然而表达式中涉及运算符的优先级顺序还有括号的干扰等,我们必须转化为一种易于计算的形式,于是这里就出现了后缀表达式和前缀表达式。
二.中缀转化后缀表达式及其运算
20世纪50年代,波兰逻辑学家想到了一种不需要括号的后缀表达法,我们也把它称为逆波兰表示,所以后缀表达式又称为逆波兰表达式,中缀表达式转化后缀表达式的规则就是 运算符放置于两部分操作数之后。
例如:9+(3-1)*3+10/2 分为三部分 9 , (3-1)*3 , 10/2
按照运算规则 : [ 9 [(3-1)*3] + ] [10/2] +
继续由大部分到小部分看为一个整体拆分 : [ 9 [ (3-1) 3 * ] +] [10 2 /] +
继续:[9 [ (3 1 -)3 * ] +] [10 2 /] +
去括号 : 9 3 1 - 3 * + 10 2 / + 后缀表达式。
1. 后缀表达式的运算
(1) 由中缀转化后缀表达式的规则和过程可知: 越是关系密切的两个操作数,其之间的运算符一定紧跟它俩之后,运算完成的数字有可以作为一个操作数。所以这里我们使用栈来完成运算。
栈求值规则:
后缀表达式从前往后扫;遇到数字入栈;
遇到运算符,选择栈顶两元素出栈进行相应运算,将运算结果再入栈。继续扫
最终运算结果为栈顶元素
注:细节问题:<1>负数判断 <2>小数判断 <3> 带'+'的数字转化 <4>多位数字转化整数(123这样的数字)<5>不合规则的串判断
(2) 代码(整数版本)
void init(){
len = 0;
Priority['('] = 0;Priority['+'] = Priority['-'] = 1;Priority['*'] = Priority['/'] = 2;Priority['^'] = 3;//优先级
while(!op.empty())op.pop();
while(!num.empty())num.pop();
}
int calculate(int &now){//字符串转换数字,同时移动i(引用)
int sum = 0;
for(;now<len-1&&ans[now]>='0'&&ans[now]<='9';now++){
sum*=10;
sum+=(ans[now] - '0');
}
return sum;
}
int Union(int a,int b,char c){//运算
if(c=='+')return a+b;
else if(c=='-')return a-b;
else if(c=='*')return a*b;
else if(c=='/')return a/b;
else return (int)(pow(a,b)+0.5);
}
void solve(){//求 9 3 1 - 3 * + 10 2 / + 的结果
int i = 0;
while(i<len-1){
if(ans[i]==' '){//空格跳过(分割符号)
i++;
continue;
}
if(ans[i]=='-'&&ans[i+1]>='0'&&ans[i+1]<='9'){//处理负数
i++;//跳到数字
int sum = calculate(i);//抽出数字
num.push(-sum);//入栈
}
else if(ans[i]>='0'&&ans[i]<='9'){//无符号整数
int sum = calculate(i);
num.push(sum);
}
else{//运算符
int a = num.top();//抽出栈顶两元素运算
num.pop();
int b = num.top();
num.pop();
int sum = Union(b,a,ans[i]);
//cout<<b<<ans[i]<<a<<" = "<<sum<<endl;
num.push(sum);//入栈
i++;
}
}
printf("%d\n",num.top());//输出结果
}
2. 中缀表达式转化后缀表达式
(1)我们要明确以下运算规则:
<1>中缀表达式中的数字出现顺序在后缀表达式中不变
<2>先算乘除后算加减,右括号先算括号里的。所以按照后缀表达式的运算规则,优先级高的运算符肯定比相邻低运算符先放在前面,括号里的优先输出。
然后由上我们得到转化规则:
<1>中缀表达式从前往后扫,遇到数字就输出
<2>遇到运算符,先判断:
若是' ( ' :则直接入栈
若是 ' ) ' :把栈中直到 ' ( ' 之前的运算符全部输出,同时删掉' ( '
若是其他运算符,若优先级高于栈顶元素,入栈。若优先级低于栈顶元素,则把栈中高于该元素的运算符全部输出,直到该 元素高于栈顶元素,入栈。
<3>最后,将栈中剩余运算符依次输出(优先级由高到低)
(2)代码(有小数):
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 100000 + 7;
char str[maxn],ans[maxn];
int len,res;
stack<char> op;
map<char,int> Priority;
void init(){
len = 0;
Priority['('] = 0;Priority['+'] = Priority['-'] = 1;Priority['*'] = Priority['/'] = 2;Priority['^'] = 3;//优先级
while(!op.empty())op.pop();
}
int Link(int st,int en){//抽离数字
int i;
for(i = st;i<en&&((str[i]>='0'&&str[i]<='9')||str[i]=='.');i++){//注意小数
ans[len++] = str[i];
}
ans[len++] = ' ';
return i;
}
void PopStack(){//')'出栈处理
while(!op.empty()&&op.top()!='('){
char ch = op.top();
op.pop();
ans[len++] = ch;
ans[len++] = ' ';
}
op.pop();
}
void PushStack(char c){//运算符入栈规则处理
while(!op.empty()&&Priority[op.top()]>=Priority[c]){
char ch = op.top();
op.pop();
ans[len++] = ch;
ans[len++] = ' ';
}
op.push(c);
}
void TransForm(){
int l = strlen(str);
int i = 0;
while(i<l){//从左往右扫
if(str[i]>='0'&&str[i]<='9'){//遇到无符号整数
i = Link(i,l);//抽出数字加空格
}
else if((str[i]=='-'&&(i==0||str[i-1]=='(')) || (str[i]=='+'&&(i==0||str[i-1]=='('))){//遇到负数或者带'+'的数字
if(str[i]=='-')ans[len++] = str[i];//'+'不添加
i++;
i = Link(i,l);//抽出数字加空格
}
else if(str[i]=='('){//左括号入栈
op.push(str[i]);
i++;
}
else if(str[i]==')'){//右括号出栈
PopStack();
i++;
}
else{//其他运算符按照规则
PushStack(str[i]);
i++;
}
}
while(!op.empty()){//剩余运算符
char ch = op.top();
op.pop();
ans[len++] = ch;
ans[len++] = ' ';
}
ans[len-1] = '\0';
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
init();
scanf("%s",str);
TransForm();
printf("%s\n",ans);
}
return 0;
}
//9+(3-1)*3+10/2
二.中缀转化前缀表达式及其运算
1.前缀表达式的计算求值
(1)前缀表达式的转化规则跟后缀表达式正好相反 :运算符放在两部分操作数之前。所以,优先级越高的运算符应该是越靠后的!所以这里,我们也是应该先运算优先级高的数字运算。因此,跟后缀运算恰恰相反 : 这里我们从后面往前扫!优先找到运算优先级高的!(因为是求值而已数字运算顺序无关)
运算规则:
<1>从后往前扫前缀表达式,遇到数字就入栈
<2>遇到运算符,选择栈顶两元素出栈进行相应运算,将运算结果再入栈。继续扫
<3>最终运算结果为栈顶元素
(2)代码(有小数,有符号数字):
#include <iostream>
#include<bits/stdc++.h>
#include<cstdio>
using namespace std;
const int maxn = 100000 + 7;
stack<double> num;
char str[maxn];
bool judge;
double IsNum(int &s){
double ans = 0;
int tim = 1;//因为是从后往前抽数字,抽取方法应该是651 = 1 + 5*10 + 6*100依次类推
bool flag = false;
while(s>=0&&((str[s]>='0'&&str[s]<='9')||str[s]=='-'||str[s]=='.'||str[s]=='+')){
if(str[s]=='.'){//遇到小数,当前积累的651就应该变成651/1000.0 = 0.651先,tim变回1
if(s-1<0||str[s-1]<'0'||str[s-1]>'9'){//重新从个位积累
return 0.0;//防止非法数字 .651出现
}
ans = ans/(tim*1.0);//变为小数位
tim = 1;//归1
s--;
continue;
}
else if(str[s]=='-'){//负数
if(s-1>=0&&str[s-1]!=' '){//防止非法数字,-号应该代表这个数字结束
return 0.0;
}
flag = true;//标记负数
s--;
continue;
}
else if(str[s]=='+'){//正数
if(s-1>=0&&str[s-1]!=' '){//防止非法数字,+号应该代表这个数字结束
return 0.0;
}
s--;
continue;
}
ans+=(str[s] - '0')*tim;//否则正常累积
tim*=10;//位数递增
s--;
}
if(flag)ans = -ans;
return ans;
}
double Calculate(double a,double b,char c){//运算操作数
if(c=='+')return a+b;
else if(c=='-') return a-b;
else if(c=='*') return a*b;
else return a/b;
}
int main()
{
gets(str);
int len = strlen(str);
int i = len-1;
while(i>=0){//从后往前扫
if(str[i]==' ')i--;//跳过空格
else if(str[i]>='0'&&str[i]<='9'){//因为从后往前扫的数字部分一定先遇到数字(不论负数还是小数)
double sum = IsNum(i);//抽出数字
num.push(sum);//入栈
}
else{//否则是运算符
double a = num.top();//运算规则
num.pop();
double b = num.top();
num.pop();
double res = Calculate(a,b,str[i]);
num.push(res);
i--;
}
}
printf("%.1lf\n",num.top());
return 0;
}
2.中缀表达式转化前缀表达式