最近数据结构作业有一道表达式求值的题,在网上学习了一下。
对于表达式的记法,有后缀表达式(逆波兰式),前缀表达式(波兰式0和中缀表达式。而这些定义是操作数和操作符的前后位置定义的。
前缀表达式:操作符位于操作数之前。
中缀表达式:操作符位于操作数之中。
后缀表达式:操作符位于操作数之后。
对于中缀表达式,我们利用括号来改变优先级关系。
但是:对于前缀和后缀表达式,我们通过操作符的位置来改变优先级关系。换句话说,在前缀和后缀表达式中是没有括号的。
在日常生活中,我们用到的是中缀表达式。但是,在中缀表达式中我们为了处理优先级的关系,是用括号来提高优先级,这对计算机分析表达式并求值造成了不便。
当然,可以认为中缀表达式中的括号是递归的,那我们就能用递归的方式来写算法。
我们今天要讨论一下前缀和后缀表达式,因为,我们可以利用栈这个数据结构,来高效的对前缀和后缀表达式求和。
中缀表达式转后缀表达式,我们有以下的算法:
需要注意一点的是:在后缀表达式中,运算符的越靠后,优先级越低
(1) 初始化两个栈:运算符栈S1和储存中间结果的栈S2;
(2) 从左至右扫描中缀表达式;
(3) 遇到操作数时,将其压入S2;
(4) 遇到运算符时,比较其与S1栈顶运算符的优先级:
(4-1) 如果S1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
(4-2) 否则,若优先级比栈顶运算符的高,也将运算符压入S1;
(4-3) 否则,将S1栈顶的运算符弹出并压入到S2中,再次转到(4-1)与S1中新的栈顶运算符相比较;
(注意:这里需要注意的是当前考虑运算符的左右结合性。如果是左结合,那对于相等优先级,先出现的运算符将先进行运算,即将先前的运算符弹出。如果是右结合,那对于相等优先级,后出现的运算符将先运算,所以我们不能弹出先前的运算符)
(5) 遇到括号时:
(5-1) 如果是左括号“(”,则直接压入S1;
(5-2) 如果是右括号“)”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到左括号为止,此时将这一对括号丢弃;
(6) 重复步骤(2)至(5),直到表达式的最右边;
(7) 将S1中剩余的运算符依次弹出并压入S2;
(8) 依次弹出S2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。
而对于后缀表达式,我们有以下算法计算表达式的值:
1.如果为操作数,将其压入栈。
2.如果为操作符,为双目运算符,将两个元素从栈弹出,计算结果,再压入栈。如果为单目运算符,将一个元素弹出栈,计算结果,在将其入栈。
整个的代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
struct unit;
int top1,top2;
int trans(char*s);
int trans(char s);
int prio(int op);
void toPolishNotation(char * expr);
double calc(double r, int op);
double calc(double r1,double r2, int op);
double calc_expr(void);
int main(void)
{
freopen("input.txt","r",stdin);
char expr[1000];
cin>>expr;
toPolishNotation(expr);
cout<<calc_expr();
return 0;
}
struct unit{
int type;//0表示不是数据,1表示是数据
double data;
int op;
/*
操作符类型
0 ( 1 )
2 + 3 -
4 * 5 /
6 ^ 7 sin
8 cos 9 log
10 ln */
unit(){}
unit(int t, int o, double d):type(t),op(o),data(d){}
} stack1[200],stack2[200];
/*stack1 存放最后结果, stack2存放中间结果*/
void toPolishNotation(char * expr)
{
top1 = top2 = 0;
for(int i = 0; expr[i] != '='; ){
if(isdigit(expr[i])){
char number[40];
int j = 0;
while(isdigit(expr[i]) || expr[i] == '.')
number[j++] = expr[i++];
number[j] = 0;
double data;
sscanf(number,"%lf",&data);
stack1[top1++] = unit(1,0,data);
}
else{
int op;
if(isalpha(expr[i])){
char fun[4];
int j = 0;
while(isalpha(expr[i]))
fun[j++] = expr[i++];
fun[j] = 0;
op = trans(fun);
}
else
op = trans(expr[i++]);
if(op == 1){//右括号
while(stack2[top2-1].op != 0)
stack1[top1++] = stack2[--top2];
--top2;
}
else if(op == 0)//左括号
stack2[top2++] = unit(0,op,0);
else{//将优先级大于等于当前运算符的运算符加入后缀表达式
while(top2 > 0 && prio(stack2[top2-1].op) > prio(op))
stack1[top1++] = stack2[--top2];
stack2[top2++] = unit(0,op,0);
}
}
}
while(top2 > 0){
stack1[top1++] = stack2[--top2];
}
}
double calc_expr(void)
{
top2 = 0;
for(int i = 0 ; i < top1; ++i){
if(stack1[i].type)
stack2[top2++] = stack1[i];
else{
if(stack1[i].op >= 7){
double r1 = stack2[top2-1].data;
double r2 = calc(r1,stack1[i].op);
stack2[top2-1].data = r2;
}
else{
double r1 = stack2[--top2].data;
double r2 = stack2[top2-1].data;
double r3 = calc(r2,r1,stack1[i].op);
stack2[top2-1].data = r3;
}
}
}
return stack2[0].data;
}
int prio(int op)
{
if(op == 0 || op == 1)
return 0;
else if(op == 2 || op == 3)
return 1;
else if(op == 4 || op == 5 || op == 6)
return 2;
else
return 3;
}
int trans(char *s)
{
if(strcmp(s,"sin") == 0)
return 7;
else if(strcmp(s,"cos") == 0)
return 8;
else if(strcmp(s,"log") == 0)
return 9;
else if(strcmp(s,"ln") == 0)
return 10;
}
int trans(char s)
{
switch(s){
case '(':
return 0;
case ')':
return 1;
case '+':
return 2;
case '-':
return 3;
case '*':
return 4;
case '/':
return 5;
case '^':
return 6;
}
}
double calc(double r, int op)
{
switch(op){
case 7:
return sin(r);
case 8:
return cos(r);
case 9:
return log10(r);
case 10:
return log(r);
}
}
double calc(double r1,double r2, int op)
{
switch(op){
case 2:
return r1 + r2;
case 3:
return r1 - r2;
case 4:
return r1 * r2;
case 5:
return r1 / r2;
case 6:
return pow(r1,r2);
}
}