表达式求值
2017-04-16 21:20
155 查看
表达式求值的大致思想是将有运算符优先级顺序的算术表达式转换为无运算符优先级的后缀表达式,从而求出值。
前缀表达式是不含括号的算术表达式,它将运算符写在前面,操作数写在后面(如“3+4”的前缀表达式为“+34”)。
2. 中缀表达式:
可以包含括号,并且运算符处于操作数中间的算术表达式(“3+4”的中缀表达式仍为“3+4”),中缀表达式就是人们平时使用的算术表示方法。
3. 后缀表达式(逆波兰式):
不包含括号,运算符放在两个操作数后面的算术表达式(如“3+4”的后缀表达式为“34+”)。
上述三个表达式可以互相转换,并有多种转换方法。常见的可以通过队列和栈,二叉树遍历等,下面我们介绍前一种方法。
1. 构建一个队列和一个栈;从左向右读取算术表达式,将操作数压入队列,运算符压入栈中。
2. 当读入为运算符时,有以下四种情况:
1)+或-号,则把栈中的运算符弹出,并压入队列中,直至栈为空或遇到左括号;然后将自己压入栈中。
2)*或/号,则把栈中的运算符弹出,并压入队列中,直至栈为空或遇到左括号/加号/减号;然后将自己压入栈中。
3)“)”号,则把栈中的运算符弹出,并压入队列中,直至遇到左括号(将左括号从栈中弹出,但并不将其压入队列)。
4)“(”号,将自己压入栈中。
3. 当[b]算术表达式从左到右读完时,注意把栈中所有的运算符读出,并顺序压入队列中([/b]为使运算符能压入double型的队列中,可令运算符为特殊数字)。
通过上面的三个步骤,便可将中缀表达式转换为后缀表达式。只要按序输出队列中的值,得到便是后缀表达式。
下面举个例子,将“ 3*(10+9/2)-7*2 ”转为后缀表达式,过程见下图:
在得到了经转换的后缀表达式之后,又该如何计算其结果呢?
操作步骤:
1. 构建一个队列和一个栈。队列初始时存储着后缀表达式,栈初始时为空。
2. 从队列中读取元素,若元素为操作数,则弹出队列压入栈中;如果元素为运算符,则再从栈中弹出两个操作数,三者求出结果后,将结果压入栈中。
3. 不断重复第2步,最后栈中的操作数,即为整个[b]算术表达式的值。[/b]
下面从栈的视角来看看求后缀式“ 3 10 9 2/+ * 7 2 * - ”值的过程:
后缀式求值从左到右执行,先计算“ 9 2 / ”,得到结果后替换原位置,即“ 3 10 [ 9 2 / ] ”得到“ 3 10 [ 4.5 ] ”([]为发生替换的部分);运算继续执行,由“ 3 [ 10 4.5 + ] ”得到“ 3 [ 14.5 ] ”;再由“ [ 3 14.5 * ] ”得到“ [ 43.5 ] ”;接着由“ 43.5 [ 7 2 * ] ”得到“ 43.5 [ 14 ] ”;最后一步由“ [ 43.5 14 - ] ”得到“ [ 29.5 ] ”,29.5便是最终的答案。
使用上面的思路便可以解决很多相似的题目:
简单计算器,秋实大哥搞算数,前缀式计算
文中如有不恰当之处,还望包容和指出,谢谢
三种表达式
1. 前缀表达式(波兰式):前缀表达式是不含括号的算术表达式,它将运算符写在前面,操作数写在后面(如“3+4”的前缀表达式为“+34”)。
2. 中缀表达式:
可以包含括号,并且运算符处于操作数中间的算术表达式(“3+4”的中缀表达式仍为“3+4”),中缀表达式就是人们平时使用的算术表示方法。
3. 后缀表达式(逆波兰式):
不包含括号,运算符放在两个操作数后面的算术表达式(如“3+4”的后缀表达式为“34+”)。
上述三个表达式可以互相转换,并有多种转换方法。常见的可以通过队列和栈,二叉树遍历等,下面我们介绍前一种方法。
中缀表达式 —> 后缀表达式
操作步骤:1. 构建一个队列和一个栈;从左向右读取算术表达式,将操作数压入队列,运算符压入栈中。
2. 当读入为运算符时,有以下四种情况:
1)+或-号,则把栈中的运算符弹出,并压入队列中,直至栈为空或遇到左括号;然后将自己压入栈中。
2)*或/号,则把栈中的运算符弹出,并压入队列中,直至栈为空或遇到左括号/加号/减号;然后将自己压入栈中。
3)“)”号,则把栈中的运算符弹出,并压入队列中,直至遇到左括号(将左括号从栈中弹出,但并不将其压入队列)。
4)“(”号,将自己压入栈中。
3. 当[b]算术表达式从左到右读完时,注意把栈中所有的运算符读出,并顺序压入队列中([/b]为使运算符能压入double型的队列中,可令运算符为特殊数字)。
通过上面的三个步骤,便可将中缀表达式转换为后缀表达式。只要按序输出队列中的值,得到便是后缀表达式。
下面举个例子,将“ 3*(10+9/2)-7*2 ”转为后缀表达式,过程见下图:
在得到了经转换的后缀表达式之后,又该如何计算其结果呢?
后缀表达式求值
中缀式求值需要考虑运算符的优先级顺序,而后缀式对此则无需顾虑。后缀式中的计算按运算符出现的顺序,严格的从左向右进行,不再考虑运算符的优先规则。操作步骤:
1. 构建一个队列和一个栈。队列初始时存储着后缀表达式,栈初始时为空。
2. 从队列中读取元素,若元素为操作数,则弹出队列压入栈中;如果元素为运算符,则再从栈中弹出两个操作数,三者求出结果后,将结果压入栈中。
3. 不断重复第2步,最后栈中的操作数,即为整个[b]算术表达式的值。[/b]
下面从栈的视角来看看求后缀式“ 3 10 9 2/+ * 7 2 * - ”值的过程:
后缀式求值从左到右执行,先计算“ 9 2 / ”,得到结果后替换原位置,即“ 3 10 [ 9 2 / ] ”得到“ 3 10 [ 4.5 ] ”([]为发生替换的部分);运算继续执行,由“ 3 [ 10 4.5 + ] ”得到“ 3 [ 14.5 ] ”;再由“ [ 3 14.5 * ] ”得到“ [ 43.5 ] ”;接着由“ 43.5 [ 7 2 * ] ”得到“ 43.5 [ 14 ] ”;最后一步由“ [ 43.5 14 - ] ”得到“ [ 29.5 ] ”,29.5便是最终的答案。
实现
举一道ACM的题目作为例子,表达式求值#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<vector> using namespace std; int main() { //求值可以完成小数,多位数,带符号数和带括号表达式的计算 vector<double> pp,qq,zz; //pp作为队列,用于中缀转后缀和后缀式求值 //qq作为栈,用于中缀转后缀时存储运算符 //zz作为栈,用于后缀式求值时存储操作数 char p[1005]; //存储运算表达式 int i,n; scanf("%d",&n); //输入组数 while(n--) { //中缀表达式转换为后缀表达式 scanf("%s",p); //输入运算表达式 if(strcmp(p,"0")==0) { break; } for(i=0;p[i]!='\0';i++)//从左向右读取运算表达式 { double s=0; //s表示等待压入队列的操作数 int t=-1,flag=0; //t用于记录紧跟小数点后连续0的个数 loop: while((p[i]>='0'&&p[i]<='9')||p[i]=='.')//读取连续数字或小数点,并将其作为一个操作数 { if(!flag) { flag=1; //表明需要后期处理 } if(p[i]=='.') //发现小数点 { t=0; //准备记录紧跟小数点后0的个数,这里进行初始化 } else { if(t!=-1) { t++; //紧跟在小数点后0的个数加一 } s=s*10+p[i]-'0'; //将字符操作数转为数字操作数 } i++; } if(flag) //后期处理,并将操作数压入队列中 { if(t!=-1) //存在小数点 { s=s*1.0/pow(10,t);//添加小数点 } if(flag==2) { s=-s;//变为负数 } pp.push_back(s); //将操作数压入队列中 i--; //该位为符号,并且未能记录,故在循环的下一次需要记录该位 } else if(p[i]==')') //读到右括号 { //栈中运算符压入队列,直至栈为空,或遇到左括号 while(!qq.empty()&&qq.back()!=-0.1050110) { pp.push_back(qq.back()); //将栈中运算符压入队列中 qq.pop_back(); //将运算符弹出栈 } if(!qq.empty()) { qq.pop_back(); //将左括号弹出栈 } } else if(p[i]=='(') //读到左括号 { qq.push_back(-0.1050110); //将左括号变为数字标记后压入栈 } else if(p[i]=='*'||p[i]=='/') //读到乘号或除号 { //栈中运算符压入队列,直至栈为空,或遇到左括号,加号,减号 while(!qq.empty()&&(qq.back()==-0.1040110||qq.back()==-0.1030110)&&qq.back()!=-0.1050110) { pp.push_back(qq.back()); //将栈中运算符压入队列中 qq.pop_back(); //将运算符弹出栈 } if(p[i]=='*') { qq.push_back(-0.1040110);//将乘号变为数字标记后压入栈 } else if(p[i]=='/') { qq.push_back(-0.1030110);//将除号变为数字标记后压入栈 } } else if(p[i]=='+'||p[i]=='-') //读到加号或减号 { //当该位处于运算表达式第一位或该位前面还有运算符(即该位表示正负) if(i==0||p[i-1]=='+'||p[i-1]=='-'||p[i-1]=='*'||p[i-1]=='/'||p[i-1]=='(') { if(p[i]=='-') { flag=2;//负数标记 } i++; //移向表达式下一位 goto loop; //跳到连续读取数字处 } //栈中运算符压入队列,直至栈为空,或遇到左括号 while(!qq.empty()&&qq.back()!=-0.1050110) { pp.push_back(qq.back()); //将栈中运算符压入队列中 qq.pop_back(); //将运算符弹出栈 } if(p[i]=='+') { qq.push_back(-0.1020110);//将加号变为数字标记后压入栈 } else if(p[i]=='-') { qq.push_back(-0.1010110);//将减号变为数字标记后压入栈 } } } while(!qq.empty())//将栈中所有运算符弹出并按序压入队列 { pp.push_back(qq.back()); qq.pop_back(); } //打印该运算表达式的后缀表达式 /*cout<<"中缀表达式转换为后缀表达式:"; for(i=0;i<pp.size();i++) { if(pp[i]==-0.1040110) { cout<<"*"<<" "; } else if(pp[i]==-0.1030110) { cout<<"/"<<" "; } else if(pp[i]==-0.1020110) { cout<<"+"<<" "; } else if(pp[i]==-0.1010110) { cout<<"-"<<" "; } else { cout<<pp[i]<<" "; } } cout<<endl;*/ //后缀表达式计算结果 for(i=0;i<pp.size();i++) //遍历后缀表达式的队列 { double a,b; if(pp[i]==-0.1010110) //运算符为减号 { a=zz.back(); //从栈中取出第一个操作数 zz.pop_back(); //弹出栈 b=zz.back(); //从栈中取出第二个操作数 zz.pop_back(); //弹出栈 zz.push_back(b-a);//将操作数运算结果压入栈中 } else if(pp[i]==-0.1020110)//运算符为加号 { a=zz.back(); zz.pop_back(); b=zz.back(); zz.pop_back(); zz.push_back(b+a); } else if(pp[i]==-0.1030110)//运算符为除号 { a=zz.back(); zz.pop_back(); b=zz.back(); zz.pop_back(); zz.push_back(b*1.0/a); } else if(pp[i]==-0.1040110)//运算符为乘号 { a=zz.back(); zz.pop_back(); b=zz.back(); zz.pop_back(); zz.push_back(b*a); } else//非运算符 { zz.push_back(pp[i]);//将操作数压入栈中 } } //cout<<"计算结果:"; printf("%.2lf\n",zz.back());//栈中的值即为表达式的值 pp.clear();qq.clear();zz.clear();//清理 } return 0; }
使用上面的思路便可以解决很多相似的题目:
简单计算器,秋实大哥搞算数,前缀式计算
扩展
根据上述的算法思想,并结合Java的Swing包,我编写了一款计算器工具(注释并不齐全,见谅)。文中如有不恰当之处,还望包容和指出,谢谢