今天来写一篇表达式求值的博客
题目的大意大家应该都知道的,例如输入3+5*2 输出13,虽然看起来很简单,但是如何让电脑也会做呢?
总不能挨个遍历,先算乘除吧后加减吧,不过只要想想知道这是行不通的,(如果这个都想不明白,可能吗?答案很显然是不可能),上面的例子就是中缀表达式,什么是中缀表达式呢,就是操作数在操作符的两边(换句话说就是大家平常看见的表达式),那前缀表达式和后缀表达式又是什么呢?
顾名思义操作符分别在前面和后面的表达式,让计算机来算表达式的话,中缀表达式肯定是不行的,只有将其变为后缀表达式,这是计算表达式的第一步,一共也就两步,下一步是将后缀表达式转化为结果。
接下来就着重的介绍如何将中缀表达式变为后缀表达式
代码若是不好懂,请先看看下面的文字说明;在回头看一下代码。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
#include"algorithm"
#include"stack"
#define maxnum 100
using namespace std;
int flag;
char a[maxnum],b[maxnum],d[maxnum],e[maxnum];
double c[maxnum];
stack<char> p1;
stack<double> p2;
int pd(char ch)//判断一个字符是否为数字和小数点
{
if((ch>='0'&&ch<='9')||ch=='.')
return 1;
return 0;
}
int priority(char ch)// 判断操作符的优先级
{
switch(ch)
{
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default :
return 0;
}
}
void zh(char a[],char b[])//中缀变后缀a用来存放中缀,b存放后缀
{
int n,i,j;
j=0;
for(i=0; a[i]!=0; i++)
{
if(pd(a[i]))
{
b[j]=a[i];
j++;
}
else
{
if(p1.empty())
p1.push(a[i]);
else
{
if(a[i]==')')
{
while(p1.top()!='(')
{
b[j]=p1.top();
j++;
p1.pop();
}
p1.pop();
}
else if(a[i]=='('||priority(a[i])>priority(p1.top()))
p1.push(a[i]);
else
{
while(!p1.empty()&&p1.top()!='('&&priority(a[i])<=priority(p1.top()))
{
b[j]=p1.top();
j++;
p1.pop();
}
p1.push(a[i]);
}
}
}
}
while(!p1.empty())
{
b[j]=p1.top();
j++;
p1.pop();
}
}
关看代码的话,其实应该也能明白的,在看中缀变后缀的函数前,先得认识int pd(char ch)//判断一个字符是否为数字和小数点 int priority(char ch)// 判断操作符的优先级 ,相信这两个函数只要有一些C语言的基础,肯定OK的,接下来我就来说说将中缀表达式变为后缀表达式的方法,我相信结合方法和代码大家一定就能明白如何将一个中缀表达式表为后缀表达式的
规则:从左到右遍历中缀表达式的每个数字和符号,若是数字则输出,即成为后缀表达式的一部分;若是符号,则判断与栈顶符号的优先级,是右括号或符号不高于栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出和,并将当前符号进栈(注意当前符号是进栈,并没有输出),一直到最终输出后缀表达式为止。
可能大家一遍看到这个规则有些苦涩难懂(大神请忽略),我也是,但是请大家不要放弃,大家先不要看代码例如9+(3-1)*3+10/2这个中缀表达式,大家先尝试着将其变为后缀表达式
大家如果能够自己写出来,那么我相信四则运算表达式求值应该就是OK的了。如果没有看出来,也不用担心下面我就来带大家一步一步的将这个例子转化成后缀表达式(提醒一下,不能够着急一定要心平气和,这时候如果你有点烦的话,建议出去放接下来的松一下,这样才会有更好的心态)
1.初始化一空栈,用来对符号进出栈使用。
2.第一个字符是9,按照规则,直接输出,即转化后的后缀表达式的第一个字符是9,紧跟着的是‘+’,显然直接进栈。
3.第三个字符是‘(’,仍然是符号,更是一个左括号,没有匹配,故直接进栈。个人提示:左括号你可以把它当做一个“栈底”。
4.接下来的是字符的3,直接输出,接着是‘-’,显然进栈。
5.接下来是数字1 ,输出,则总输出的表达式为9 3 1,后面的的是符号‘)’,此时将前面一个’(‘内的内容全部输出,由于括号内只有一个’-‘,直接输出’-‘,此时表达式为9 3 1 -;符号栈中的元素为‘+’。
6.紧接着是‘’,此时栈顶元素为‘+’,乘号的优先级高于加号,根据规则,乘号入栈。下一个字符是数字3,显然直接输出,此时的表达式为9 3 1 - 3。符号栈中从栈底到栈顶为‘+ ’;
7.之后的符号是‘+’,显然没有比加号更低的操作符了,故全部出栈后,加号入栈,此时的表达式为9 3 1 - 3 * +;符号栈中就一个加号了;
8.接下来是数字10,输出,总表达式为9 3 1 - 3 * + 10。后面是‘/’,所以除号进栈。
9.最后一个数字是2,输出,总表达式为 9 3 1 - 3 * + 10 2 。
10.到了最后了,符号栈中的元素全部出栈,所以最终表达式为9 3 1 - 3 * + 10 2 / +;
相信大家对于如何将一个中缀表达式变为后缀表达式已经有所了解了。
接下来有个问题了,由于表达式是有字符数组存放的,转化后的表达式就很容易给人歧义,假如案例改为
9+(3-1)3+12/2,那么对应的后缀表达式变为9 3 1 - 3 + 12 2 / +;那么怎么确定是12 2还1 22呢?也有人说每入一个后,在其后缀表达式加一个空格,例如9 3 1 - 3 * + 12 2 / + ,这样就十分的明显了,但是就有一个问题了,例如985.3+72,显然后缀表达式为985.3 72 +;
可是按照刚刚的规则遇到数字就直接输出,9 8 5 . 3 + 7 2;,所以我们要确定985.3是一个整体,985.3作为一个数字要直接输出,不能挨个作为独立的;
说到这,我相信大家再回头看代码一定就不会吃力了,加上代码上的函数一些注释,大家就应该轻而易举了。
接下来就是第二部分了,如何计算后缀表达式。接着刚刚的例子9 3 1 - 3 * + 12 2 / + ;
先说规则:从左到右遍历表达式的每一个数字和符号,遇到数字就进栈,遇到符号就,就将栈顶的两个数进行符号运算,将结果入栈就OK了,这个我就不一步一步来带大家看了,很简单的,大家只需要动一下脑子就OK了
这个表达式计算的结果是20,大家自己算一下吧。
接下来给出完整的代码
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
#include"algorithm"
#include"stack"
#define maxnum 100
using namespace std;
int flag;
char a[maxnum],b[maxnum],d[maxnum],e[maxnum];
double c[maxnum];
stack<char> p1;
stack<double> p2;
int pd(char ch)//判断一个字符是否为数字和小数点
{
if((ch>='0'&&ch<='9')||ch=='.')
return 1;
return 0;
}
int priority(char ch)// 判断操作符的优先级
{
switch(ch)
{
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default :
return 0;
}
}
void zh(char a[],char b[])//中缀变后缀
{
int n,i,j;
j=0;
for(i=0; a[i]!=0; i++)
{
if(pd(a[i]))
{
b[j]=a[i];
j++;
}
else
{
if(p1.empty())
p1.push(a[i]);
else
{
if(a[i]==')')
{
while(p1.top()!='(')
{
b[j]=p1.top();
j++;
p1.pop();
}
p1.pop();
}
else if(a[i]=='('||priority(a[i])>priority(p1.top()))
p1.push(a[i]);
else
{
while(!p1.empty()&&p1.top()!='('&&priority(a[i])<=priority(p1.top()))
{
b[j]=p1.top();
j++;
p1.pop();
}
p1.push(a[i]);
}
}
}
}
while(!p1.empty())
{
b[j]=p1.top();
j++;
p1.pop();
}
}
int fenjie(char a[])//求出表达式各数的大小以及位数
{
int i,j=0,k=0,t,n=0;
char num[10];
for(i=0; a[i]!=0; i++)
{
if(a[i]=='('||a[i]==')')
continue;
if(pd(a[i]))
{
num[j]=a[i];
if(a[i]=='.')
n=j;
j++;
}
else
{
if(n==0)
{
for(t=0; t'0')*pow(10,j-t-1);
}
else
{
for(t=0; t'0')*pow(10,n-t-1);
for(t=n+1; t'0')*pow(10,n-t);
}
d[k]=j;
j=0;
k++;
n=0;
memset(num,0,sizeof(num));
}
}
if(n==0)
{
for(t=0; t'0')*pow(10,j-t-1);
}
else
{
for(t=0; t'0')*pow(10,n-t-1);
for(t=n+1; t'0')*pow(10,n-t);
}
d[k]=j;
j=0;
k++;
n=0;
memset(num,0,sizeof(num));
return k;
}
double jg(char b[])//由后缀表达式求出结果
{
int i,j=0;
double x,y,z;
for(i=0; b[i]!=0&&flag; i++)
{
if(pd(b[i]))
{
p2.push(c[j]);
i=i+d[j]-1;
j++;
}
else
{
y=p2.top();
p2.pop();
x=p2.top();
p2.pop();
switch(b[i])
{
case '+':
z=x+y;
break;
case '-':
z=x-y;
break;
case '*':
z=x*y;
break;
case '/':
if(y==0)
flag=0;
else
z=x/y;
break;
}
p2.push(z);
}
}
z=p2.top();
while(!p2.empty())
p2.pop();
return z;
}
void print(double z)
{
int x;
z=z*100;
x=z;
if(x%10==0&&x/10%10==0)
printf("=%.0lf\n\n",z/100);
else
printf("=%.2lf\n\n",z/100);
}
int is_priority(char ch)
{
if(ch=='+'||ch=='-'||ch=='*'||ch=='/'||ch=='.')
return 1;
else
return 0;
}
int is_error(char a[])
{
int i,n=0;
memset(e,0,sizeof(e));
if(is_priority(a[0])||a[0]==')')
return 1;
for(i=0; a[i]!=0; i++)
{
if(a[i]!='('&&a[i]!=')')
e[i]=a[i];
}
if(is_priority(a[i-1]))
return 1;
for(i=0; e[i]!=0; i++)
{
if(is_priority(e[i])&&is_priority(e[i+1]))
return 1;
}
for(i=0; a[i]!=0; i++)
{
if(a[i]=='('&&((is_priority(a[i+1]))||a[i-1]=='.'))
{
return 1;
}
if(a[i]==')'&&((is_priority(a[i-1]))||a[i+1]=='.'))
{
return 1;
}
}
for(i=0; a[i]!=0; i++)
{
if(a[i]=='(')
n++;
else if(a[i]==')')
n--;
if(n<0)
return 1;
}
if(n!=0)
return 1;
return 0;
}
int main()
{
int i,j,k;
double z;
while(1)
{
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
memset(d,0,sizeof(d));
flag=1;
scanf("%s",a);
getchar();
if(is_error(a))
{
printf("表达式不符合规则!请重新输入\n\n");
continue;
}
k=fenjie(a);
zh(a,b);
/*for(i=0;b[i]!=0;i++)
printf("%c",b[i]);
printf("\n");*///输出其后缀表达式
z=jg(b);
if(flag)
{
printf("%s",a);
print(z);
}
else
printf("表达式不符合规则!请重新输入\n\n");
}
return 0;
}
//2*(3+1) 9+(3-1)*2+4/2 6.23+(5.54+2.39)*7.4
//53.5*34.6-15.8
代码看起来有点多,所以大家一定要耐着性子看,代码还有错误检查,就例如你输入9+10/0或者9+(1+3或者+10-5会报错,不过没有给出错误类型,如果你想做一个会报错并且给出错误类型的代码也是未尝不可的,有兴趣的可以尝试一下
代码的最后给了两个输入案例