目录
方法一:将中缀表达式转化为后缀表达式,再计算后缀表达式得出结果
1. 算术表达式的计算方法
人类在进行四则运算时,会选用最直观的中缀表达式进行计算。这种表达式的特点是运算符在两个运算数字的中间,符合人类的思维习惯。
但是,由于计算机严格从左向右读入数据的特性,导致其无法很好地处理加减乘除的优先级、优先计算括号等问题,因此使用计算机求解算式时并不能使用中缀表达式。
对于该问题,有几种解决方法:
方法一:将中缀表达式转化为后缀表达式,再计算后缀表达式得出结果
该方法的原理为使用一个栈暂时储存运算符,同时将数值与处理后的运算符放到队列中。
① 遇到数值,将其放入队列;
② 遇到 '(' ,将其放入栈;
③ 遇到运算符,则比较该运算符与栈顶运算符的优先级:若栈顶运算符比该运算符优先级低,则入栈;若栈顶运算符比该运算符优先级高或与该运算符优先级相同,则不断弹出栈中运算符进入队列,直到栈顶运算符比该运算符优先级低或栈为空,满足条件后将该运算符入栈。
④ 若遇到 ')' ,则依次弹出栈中的运算符进入队列,直到遇到 '(' ;
⑤ 当运算表达式读入结束时,依次弹出栈中的所有运算符进入队列,直到栈为空。
如:中缀表达式 A + ( B - C / D ) * E 相对应的后缀表达式为 A B C D / - E * +
以上步骤完成了表达式的转化,接下来进行后缀表达式的计算:
新建一个栈来进行值的计算。 从左向右扫描后缀表达式,若遇到操作数,则压入该栈;若遇到运算符,则将栈顶的两个操作数弹出栈进行计算,再将结果压入栈。不断进行此过程直到结束,将栈中唯一的元素弹出栈。
表达式 A B C D / - E * + 的计算流程如下图所示。
方法二:使用双栈算符优先级法进行计算
该方法需要建立两个栈,分别为运算数栈与运算符栈。与方法一不同的是,该方法并没有严格的“转化表达式”与“对转化后的表达式进行运算”之分,更像是同时进行。
该方法的具体流程为:
① 遇到运算数,直接将其放入运算数栈;
② 遇到 '(' ,直接将其放入运算符栈;
③ 遇到运算符,则比较其与栈顶运算符的优先级:若该运算符比栈顶运算符优先级高,则直接将其放入运算符栈;若该运算符比栈顶运算符优先级低或与栈顶运算符优先级相同,则运算数栈弹出两个运算数、运算符栈弹出一个运算符,进行一次运算,将运算结果放入运算数栈,该过程结束后将该运算符放入运算符栈;
④ 遇到 ')' ,则不断弹出两个运算数与一个运算符进行运算,结果放入运算数栈中,直到遇到 '(' ;
⑤ 当运算表达式读入结束时,则不断弹出两个运算数与一个运算符进行运算,结果放入运算数栈中,直到运算符栈为空。
方法三:用二叉树来求解后缀表达式的值
使用该方法计算表达式 A B C D / - E * + 的流程如下图所示。
该方法需要用到较为复杂的二叉树结构,且编程实现工程量较大,故不多赘述。
2. 双栈算符优先级法的代码实现
以上三种方法各有特点与优劣。但相较于其他两种方法,双栈算符优先级法流程更加清晰、数据分类更加明确,因此选择双栈算符优先级法进行代码实现。
以下为双栈算符优先级法求解算术表达式的实现代码:
#include <bits/stdc++.h>
using namespace std;
stack<double> num;
stack<char> op;
bool is_number(string s, int i)
{
if((s[i]>='0' && s[i]<='9') || s[i]=='.') return 1;
return 0;
}
bool is_new(string s, int i)
{
if((!is_number(s, i-1)) || i==0) return 1;
return 0;
}
double calc(double x, double y, char op)
{
switch(op)
{
case '+' : return(x+y);
case '-' : return(x-y);
case '*' : return(x*y);
case '/' : return(x/y);
}
}
bool is_prior(char newop, char op)
{
if((newop=='*' || newop=='/') && (op=='+' || op=='-')) return 1;
if(op=='@' || op=='(') return 1;
return 0;
}
int main()
{
string s;
int flag = 0;
double n;
cin >> s;
op.push('@');
num.push(0);
for(int i=0; i<s.length(); i++)
{
if(is_number(s, i))
{
if(is_new(s, i))
{
num.push(s[i]-'0');
}
else
{
if(s[i]=='.')
{
flag = 1;
continue;
}
if(!flag)
{
num.top() = num.top()*10+s[i]-'0';
}
else
{
num.top() = num.top()+(s[i]-'0')/pow(10, flag);
flag++;
}
}
}
else
{
flag = 0;
if(s[i]=='(')
{
op.push(s[i]);
}
else if(s[i]==')')
{
while(op.top()!='(')
{
n = num.top();
num.pop();
num.top() = calc(num.top(), n, op.top());
op.pop();
}
op.pop();
}
else if(s[i]=='=')
{
while(op.top()!='@')
{
n = num.top();
num.pop();
num.top() = calc(num.top(), n, op.top());
op.pop();
}
}
else
{
if(is_prior(s[i], op.top()))
{
op.push(s[i]);
}
else
{
while(!is_prior(s[i], op.top()))
{
n = num.top();
num.pop();
num.top() = calc(num.top(), n, op.top());
op.pop();
}
op.push(s[i]);
}
}
}
}
cout << num.top();
return 0;
}
对于完全正确的输入,该代码可以给出正确的结果。效果如图所示:
但这份代码并不完整。首先,它无法进行含有负数的运算;其次,它不具备判断输入错误的能力,如括号不匹配、小数点使用错误、运算符连用等,需要进一步修改与完善。