转自:点击打开链接 海子
中缀表达式求值问题
中缀表达式的求值问题是一个比较常见的问题之一,我们通常在编写程序时,直接写出表达式让编译器去处理,很少去关心编译器是怎么对表达式进行求值的,今天我们来一起了解一下其中具体的原理和过程。
表达式一般来说有三种:前缀表达式、中缀表达式、后缀表达式,其中后缀表达式又叫做逆波兰表达式。中缀表达式是最符合人们思维方式的一种表达式,顾名思义,就是操作符在操作数的中间。而前缀表达式和后缀表达式中操作符分别在操作数的前面和操作数的后面。举个例子:
3+2
这个是最简单的一个中缀表达式。而其等同的前缀表达式形式为+32,后缀表达式形式为32+。
那么一些朋友可能会问既然中缀表达式最符合人类的思维习惯,为什么还需要前缀表达式和后缀表达式?先看一个例子,假如在前面的表达式基础上加一点东西:
3+2*5
此时的表达式很显然,如果进行计算,则先计算2*5,最后计算加法。但是如果需要先计算加法运算呢?则必须加上括号,(3+2)*5。
而如果用后缀表达式来表示,则为 32+5*,那么该表达式的计算顺序为3+2 —> (3+2)*5。
区别就在这里,后缀表达式不需要用括号就能表示出 整个表达式哪部分运算先进行。同理,前缀表达式也是如此。这种表达式正好最符合计算机的处理方式,因为后缀表达式和前缀表达式求值不需要考虑优先级的问题,计算机处理起来便简单很多。
今天我们这里主要讲解中缀表达式和后缀表达式(前缀表达式和后缀表达式很类似,就不做过多赘述),下面是讲解大纲:
1.中缀表达式直接求值
对于中缀表达式求值来说,一般最常见的直接解决办法就是利用栈,一个栈用来保存操作数,一个栈用来保存操作符。
为了简便起见,暂时表达式中只考虑简单的+,-,*,/运算,只有圆括号,并且都是整数。
假如有这样一个表达式:((3+5*2)+3)/5+6/4*2+3
对于这样一个表达式,如果让你来设计操作数和操作符进栈的出栈的规则,你会怎么设计?
先不看这么复杂的表达式,考虑一下简单点的,还是前面的3+2*5,那么很显然先进行乘法运算,后进行加法运算,但是由于操作符在操作数中间,所以当一个操作符进操作符栈时,该操作符的两个操作数并没有都进入到操作数栈中,那么如何解决呢?只有在后面一个操作符进操作符栈时,前面的一个操作符所作用的两个操作数才会全部进栈。比如3+2*5,栈的变化过程为:
操作数栈:3 操作数栈:3 操作数栈:3 2
操作符栈:空 操作符栈:+ 操作符栈:+
注意此时遇到操作符“*”,是不是需要弹出操作数栈中的两个操作数进行运算呢,很显然不是,因为乘法运算法比操作符栈的栈顶运算符优先级高,也就是说当前的操作符在“+”前进行运算,那么还需要将当前操作符压栈,则变成:
操作数栈:3 2 操作数栈:3 2 5
操作符栈:+ * 操作符栈:+ *
此时到了表达式的结尾,既然栈顶的操作符的优先级比栈底的操作符的优先级高,那么可以取操作符栈的栈顶操作符和操作数栈的栈顶两个元素进行计算,则得到2*5=10,(注意从操作数栈先弹出的操作数为右操作数)。此时得到10 ,则应该把10继续压到操作数栈中,继续取操作符栈的栈顶操作符,依次进行下去,则当操作符栈为空时表示计算过程完毕,此时操作数栈中剩下的唯一元素便是整个表达式的值。
再换个例子:2*5+3,这个表达式跟前面表达式的结果虽然相同,但是操作数和操作符入栈和出栈的顺序发生了很大变化:
操作数栈:2 操作数栈:2 操作数栈:2 5
操作符栈:空 操作符栈:* 操作符栈:*
此时遇到“+”,而操作符栈的栈顶操作符为“*”,栈顶操作符优先级更高,表示此时可以取操作符栈顶操作符进行运算,那么栈变成:
操作数栈:10 操作数栈:10 3
操作符栈:空 操作符栈:+
后面的过程跟前面一个例子类似。
如果复杂一点,比如包含有括号,连续的乘除法这些怎么处理呢?道理是一样的,对于左括号直接入栈,碰到右括号,则一直将操作符退栈,直到碰到左括号,则括号中的表达式计算完毕。对于连续的乘除法,跟前面例子中处理过程类似。只需要记住一点:只有当前操作符的优先级高于操作符栈栈顶的操作符的优先级,才入栈,否则弹出操作符以及操作数进行计算直至栈顶操作符的优先级低于当前操作符,然后将当前操作符压栈。当所有的操作符处理完毕(即操作符栈为空时),操作数栈中剩下的唯一一个元素便是最终的表达式的值。而操作符的优先级为:+和-优先级是一样的,*和/优先级是一样的,+、-的优先级低于*、/的优先级。
不过需要注意的是在求值之前需要对表达式进行预处理,去掉空格、识别 负号(区分“-”是作为减号还是负号),提取操作数等。
对于“-”的区分,主要判别方法为:
1)若前一个字符为‘(',则必定为负号;
2)若前一个字符为')'或者数字,则必定为减号;
3)若前面一个字符为其他运算符,如*,/,则必定是负号;
3)若前面没有字符,即该字符为表达式的第一个字符,则必定是负号。
也就是说只有一种情况下,”-“是作为减号使用的,就是前一个字符为')'或者数字的时候。
如果判断出“-”是作为负号使用的,这里我采用“#”来代替“-”,并将其作为一种运算(优先级最高)。比如:-3*2
我采取的做法是将"#"入栈,然后当遇到“*”时,由于栈顶操作符为"#",因此取#,然后取操作数栈的栈顶元素(只取一个)进行运算,然后再把结果压栈。
#include <cstdio> #include <cstring> #include <stack> #define LL long long using namespace std; #define maxn 5005 char s[maxn]; LL num[maxn]; char sig[maxn]; int nn,sn; int pri(char c){ if(c=='+'||c=='-') return 1; if(c=='*'||c=='/') return 2; if(c=='(') return 0; } void cal(){ if(sig[sn]=='+') num[nn-1]=num[nn]+num[nn-1]; if(sig[sn]=='-') num[nn-1]=num[nn-1]-num[nn]; if(sig[sn]=='*') num[nn-1]=num[nn-1]*num[nn]; if(sig[sn]=='/') num[nn-1]=num[nn-1]/num[nn]; nn--; sn--; } LL solve(int l,int r){ nn=sn=0; int curn=0; for(int i=l;i<=r;i++){ if(isdigit(s[i])){ curn=curn*10+s[i]-'0'; } else { if(s[i]=='(') { sig[++sn]=s[i]; continue; } if(s[i-1]!=')'){ num[++nn]=curn; curn=0; } if(s[i]==')'){ while(sig[sn]!='(') cal(); sn--; continue; } if(sn&&pri(s[i])<=pri(sig[sn])) cal(); sig[++sn]=s[i]; } } if(s[r]!=')') num[++nn]=curn; while(nn>1) cal(); return num[1]; } int main(){ while(~scanf("%s",s)){ cout<<solve(0,strlen(s)-1)<<endl; } return 0; }