计算中缀表达式
题目描述:
读入一个只包含+,-, *,/,(,)的整数计算表达式,计算该表达式的值,输出结果保留两位小数(对于除法的运算是有可能带有小数的)。输出结果在1e9范围内。
例如:
输入:5+(10*2)-6
输出:19.00
解题思路
这里只讲最经典的一种解法:先将中缀表达式转化为后缀表达式,再由后缀表达式进行计算。
为什么要先转化成后缀表达式而不直接进行计算:
因为中缀表达式涉及了运算符优先级,还涉及了括号优先级,虽然人脑可以轻易判断各种运算符优先级,但是在打代码时,判断优先级会非常麻烦。再加上需要将“连续的数字字符”转化成对应的整数型操作数(例如将‘1’‘2’‘3’转化成123),就更加麻烦。(不信可以自己先想想直接用中缀表达式怎么计算。)
如果先将中缀表达式转化成不带括号的后缀表达式在进行计算,这样对于计算机(我们打代码时)会更简单。
中缀表达式转后缀表达式:
现在的第一个问题是,怎么将中缀表达式转为后缀表达式。
我们在草稿本上手动转化时,通常做法是先将算术式中的操作数(数字)全部提出来写好,再根据运算符优先级根据后缀表达式的规则加入到其中。
那么我们在电脑上操作时,也是类似的道理。用一个队列来保存后缀表达式,用一个栈来保存操作符。具体流程步骤如下:
1.先从左到右遍历中缀表达式,将遇到的操作数依次存入后缀表达式队列。注意:必须先将操作数转化成十进制再存入后缀表达式队列,例如,碰到‘1’‘2’‘3’,先将其转化成(十进制)123,再存入后缀表达式队列。
将连续的ASCII码转化成10进制数字的函数:
ElementType transFormation(int &i,int len)
{
ElementType num;
num = str[i++] - '0';
for(;i<len&&str[i]>='0'&&str[i]<='9';i++)
num = num*10+(str[i]-'0');
i--;//必须把最后一次i++抵消掉
return num;
}
2.遇到操作符时,根据操作符优先级:
如果操作符为'(',则直接存入操作符栈(因为'('优先级最高),遇到')',则将操作符栈中'('后面的操作符全部出栈并存入后缀表达式队列。
如果遇到'+','-','*','/',则考虑优先级,先将栈中保存的优先级大于等于该操作符的操作符弹出栈并存入后缀表达式队列,然后再将该操作符压入栈。
这里使用map键值对存操作符优先级:
priority.clear();
priority['+'] = priority['-'] = 1;
priority['*'] = priority['/'] = 2;
将中缀表达式转为后缀表达式模块的代码:
//初始化优先级
inline void init()
{
priority.clear();
priority['+'] = priority['-'] = 1;
priority['*'] = priority['/'] = 2;
while(!suffix.empty()) suffix.pop();
while(!operation.empty()) operation.pop();
}
//取得后缀表达式队列
inline void getSuffix()
{
Node temp;
int len = str.length();
for(int i = 0;i<len;i++){
if(str[i] == '('){
//'('无需比较,直接进栈
temp.flag = false;
temp.op = str[i];
operation.push(temp);
}
if(str[i] == ')'){
//')',操作符依次出栈,直到遇到'('
while(operation.top().op != '('&&!operation.empty()){
suffix.push(operation.top());
operation.pop();
}
//弹出'('
operation.pop();
}
if(str[i] == '+'||str[i] == '-'||str[i] == '*'||str[i] == '/'){
temp.flag = false;
temp.op = str[i];
//首先弹出栈中优先级大于等于该操作符的操作符,注意不能遇到'(',因为map没有添加'('键
while(!operation.empty()&&priority[str[i]]<=priority[operation.top().op]&&
operation.top().op != '('){
suffix.push(operation.top());
operation.pop();
}
//然后将此操作符压入操作符栈
operation.push(temp);
}
if(str[i]>='0'&&str[i]<='9'){
//将操作数放入后缀队列,注意要先转成数字
temp.flag = true;
temp.num = transFormation(i,len);
suffix.push(temp);
}
}
//把操作符栈里剩余的添加到后缀队列中
while(!operation.empty()){
suffix.push(operation.top());
operation.pop();
}
}
计算后缀表达式:
在转化好后缀表达式后,计算就会轻松多了:
将后缀表达式中的数字压入栈,遇到操作符时,就计算栈顶的两个元素并弹出栈顶两个元素,将得到的新数字重新压入栈,循环这个过程直到后缀表达式为空。此时栈顶就是我们要的结果。
这里附一张偷的图,便于理解后缀表达式的计算:
(额,这个水印请忽略它,不是我想加的,这张图片来自上面那个链接,讲的很好,这部分可以直接去看那个链接)
代码:
这里注意要检测除法分母不能为0.
bool combine(ElementType &num,ElementType num1,ElementType num2,char op)
{
if(op == '+') num = num1+num2;
if(op == '-') num = num1-num2;
if(op == '*') num = num1*num2;
if(op == '/'){
if(!num2) return false;
num = num1/num2;
}
return true;
}
//计算后缀表达式
inline bool calculate(ElementType &ans)
{
//接下来,operation栈的作用将会改变,它现在不作为存放操作符的栈,而是存放数字的栈
while(!suffix.empty()){
Node temp = suffix.front();
suffix.pop();
if(temp.flag) operation.push(temp);
else{
Node nxt;
ElementType num2 = operation.top().num;//后来的数在上面
operation.pop();
ElementType num1 = operation.top().num;
operation.pop();
if(!combine(nxt.num,num1,num2,temp.op))//计算两个数的结合
return false;
nxt.flag = true;//要将计算好的数字重新放进栈
operation.push(nxt);
}
}
ans = operation.top().num;
return true;
}
完整代码(C++):
#include<queue>
#include<string>
#include<stack>
#include<map>
using namespace std;
typedef double ElementType;
typedef struct Node
{
ElementType num;//操作数
char op;//操作符
bool flag;//用于判断该节点是操作符还是数字
}Node;
map<char,int>priority;//优先级键值对
string str;//待处理字符串
stack<Node> operation;//操作符栈
queue<Node> suffix;//后缀表达式队列
//初始化
inline void init()
{
priority.clear();
priority['+'] = priority['-'] = 1;
priority['*'] = priority['/'] = 2;
while(!suffix.empty()) suffix.pop();
while(!operation.empty()) operation.pop();
}
//去除空格
inline void removeSpaces()
{
string::iterator iter;
for(iter = str.end();iter != str.begin();iter--)
if(*iter == ' ') str.erase(iter);
}
//转化数字
ElementType transFormation(int &i,int len)
{
ElementType num;
num = str[i++] - '0';
for(;i<len&&str[i]>='0'&&str[i]<='9';i++)
num = num*10+(str[i]-'0');
i--;//必须把最后一次i++抵消掉
return num;
}
bool combine(ElementType &num,ElementType num1,ElementType num2,char op)
{
if(op == '+') num = num1+num2;
if(op == '-') num = num1-num2;
if(op == '*') num = num1*num2;
if(op == '/'){
if(!num2) return false;
num = num1/num2;
}
return true;
}
//转化后缀表达式
inline void getSuffix()
{
Node temp;
int len = str.length();
for(int i = 0;i<len;i++){
if(str[i] == '('){
//'('无需比较,直接进栈
temp.flag = false;
temp.op = str[i];
operation.push(temp);
}
if(str[i] == ')'){
//')',操作符依次出栈,直到遇到'('
while(operation.top().op != '('&&!operation.empty()){
suffix.push(operation.top());
operation.pop();
}
//弹出'('
operation.pop();
}
if(str[i] == '+'||str[i] == '-'||str[i] == '*'||str[i] == '/'){
temp.flag = false;
temp.op = str[i];
//首先弹出栈中优先级大于等于该操作符的操作符,注意不能遇到'(',因为map没有添加'('键
while(!operation.empty()&&priority[str[i]]<=priority[operation.top().op]&&operation.top().op != '('){
suffix.push(operation.top());
operation.pop();
}
//然后将此操作符压入操作符栈
operation.push(temp);
}
if(str[i]>='0'&&str[i]<='9'){
//将操作数放入后缀队列,注意要先转成数字
temp.flag = true;
temp.num = transFormation(i,len);
suffix.push(temp);
}
}
//把操作符栈里剩余的添加到后缀队列中
while(!operation.empty()){
suffix.push(operation.top());
operation.pop();
}
}
//计算后缀表达式
inline bool calculate(ElementType &ans)
{
//接下来,operation栈的作用将会改变,它现在不作为存放操作符的栈,而是存放数字的栈
while(!suffix.empty()){
Node temp = suffix.front();
suffix.pop();
if(temp.flag) operation.push(temp);
else{
Node nxt;
ElementType num2 = operation.top().num;//后来的数在上面
operation.pop();
ElementType num1 = operation.top().num;
operation.pop();
if(!combine(nxt.num,num1,num2,temp.op))//计算两个数的结合
return false;
nxt.flag = true;//要将计算好的数字重新放进栈
operation.push(nxt);
}
}
ans = operation.top().num;
return true;
}
//检查后缀表达式
inline void printSuffix()
{
//注意!检查后后缀表达式会被清空,请单独使用该函数
while(!suffix.empty()){
Node temp = suffix.front();
suffix.pop();
if(temp.flag) cout<<temp.num<<" ";
else cout<<temp.op<<" ";
}
cout<<endl;
}
int main()
{
init();
cin>>str;
removeSpaces();//去除空格
getSuffix();//转换成后缀表达式
//printSuffix();//检查后缀表达式
ElementType ans = 0;
//计算答案
if(!calculate(ans)) cout<<"Error!Divisor cannot be 0!"<<endl;
else printf("%.2f\n",ans);
return 0;
}