前缀表达式、中缀表达式、后缀表达式的区别

一、三者的概念(参考维基百科)

1.1中缀表达式

中缀表达式是符合人类直觉的一种表达方式,其特点是操作符(二元操作符)在中间,操作数在两侧。

例如 3 + 4 ,   5 - 6 * 7,     (5 - 6) *7等。括号的存在会影响计算步骤的执行。


1.2前缀表达式(又称波兰表达式)

前缀表达式(以及后缀表达式)是符合计算机思维的一种表达方式。

将1.1的几个中缀表达式转换成前缀表达式如下:

+ 3 4 ,   - 5 * 6 7,     * - 5 6 7

这种表达式有一个特点,由它的发明人波兰数学家扬·武卡谢维奇指出,这种方式是无需括号的表达方式,即括号不影响计算顺序

如 * (- 5 6) 7 与 * - 5 6 7是一样的。这里需要说明的是,无需括号的这种特点是有适用范围的,当操作符作用的操作数的个数可变时,如阶乘一元 + - * /等为二元时,就需要用括号来表示操作顺序了。

1.3后缀表达式(逆波兰表达式)

后缀表达式也是符合计算机思维的一种表达方式,与前缀表达式具有同样的特点,只是顺序相反(在编程实现上还有些细微差别,),但是我们在编程实现的时候对一个表达式的字符串,采用后缀表达的话,字符串从前往后读取并处理,符合我们的编程习惯,所以常常以如何求值后缀表达式或将中缀表达式转换成后缀表达式来作为教学。

将1.1的几个中缀表达式转换成前缀表达式如下:

3 4 +,    5 6 7 * - , 5 6 - 7 * 


二、中缀表达式与后缀表达式的比较(基于二元操作符下的讨论)

2.1中缀表达式的朴素算法

已知运算符是有运算顺序的,对于一个没有括号的表达式(操作数记为n, 操作符记为op):

n1  op1  n2  op2  n3  op3  n4  ...  opk  nk+1

我们知道,当计算机线性读取(即从左到右读取该字符串时)其遇到op1,此时它并不能确定是否可以立即做运算,因为op1优先级不一定是最高的,所以它必须得遍历op1 到 opk找到最高级的opj,并做(nj opj nj+1)的运算。

这一步骤可归纳如下:遍历整个字符串,找到当前最高级的操作符opj,进行运算,所有运算符均参与运算。

每一步的时间复杂度为(假设当前字符串长度为n): n, 下一步则为n - 3 + 1 = n -2。

所以整个算法复杂度为等差数列求和为O(n^2)。

这也是为什么符合人类直觉的表达式却不适合用计算机来处理。


2.2具有迷惑性的中缀表达式求值算法(本质上是将中缀表达式转换成后缀表达式再处理的一种方式)

/*
此算法摘自《中缀和后缀算术表达式的分析比较》刘爱贵  高能物理研究所计算中心 2003年
*/

int middexpression(char *exp){
    stack<int> *opnd=new(stack<int>);   // 操作数栈
    stack<char> *optr=new(stack<char>);  // 运算符栈
    char ch=*exp;
    int x=0,y,z;
    int result;
    optr->push('#');
    while(ch!='#'||optr->gettop()!='#')// 字符扫描完毕且运算符栈仅有‘#’时运算结束
    {
        if(!Operator(ch)){
           x=x*10+(int)(ch)-48;
           if(Operator(*(exp+1))){
              opnd->push(x);
              x=0;
           }
           ch=*++exp;
        }
        else{
            switch(Precede(optr->gettop(),ch)) {
            case '<':// 栈顶元素优先权低
                optr->push(ch);
                ch=*++exp;
                break;

            case '=':// 脱括号并接收下一字符
                optr->pop();
                ch=*++exp;
                break;
            case '>':// 退栈并将运算结果入栈,但不取下一表达式字符
                x=opnd->pop();
                y=opnd->pop();
                z=calc(y,optr->pop(),x);
                opnd->push(z);x=0;
                break;
            }
        }
    }
    result = opnd->pop();
    return(result);
}

这里我们重点关注一下该算法时如何处理操作符的(此处摘抄参考文章的原文):

为了实现算符优先算法,要使用两个工作栈。一个称为OPTR, 用以寄存运算符;另一个称为OPND ,用以寄存操作数或运算结果,它的基本思想于后缀表达式计算的不同在于:当读取到运算符时并不可能作相应运算,必须先比较运算符栈中栈顶元素与当前运算符的优先级。若为‘< ’则运算符入栈;若为‘=’则说明是一对括号,需脱括号;若‘> ’则作相应运算并将结果入栈。

这里再列出一下将中缀表达式转换成后缀表达式的算法(摘自将中缀表达式转化为后缀表达式):

规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出,并将当前符号进栈,若是左括号或优先级高于或等于栈顶符号则直接进栈,一直到最终输出后缀表达式为止。

这里可以看到所谓的上述两个算法对操作符的处理其实是大同小异的。只不过所谓的中缀表达式求值算法将中缀表达式转换成后缀表达式的同时,也同在进行操作符的运算了。我认为其具有迷惑性的原因在于,不少博客用中缀表达式求值算法为该算法命名,如果不去解构该算法的话,会有这样一种迷惑,为什么中缀表达式求值算法已经是O(n)的复杂度了,我们还需要去讨论后缀表达式。但是通过结构该算法后,我们可以看到这个算法其实就是将中缀表达式转换成后缀表达式与后缀表达式的计算结合在了一起。

需要说明的是,我认为刘爱贵先生的这篇论文在最后的时间复杂度分析是存在些许问题的,他将该算法的复杂度分析成O(n^2)了,实际上我认为是O(n),但是我的分析过程并不是太严谨,也可能有疏漏。

2.3后缀表达式求值算法

此时的算法思想已经比较简单了:从前往后读后缀表达式,操作数则压入栈中,若遇到操作符就去操作符弹出栈顶的两个操作数做运算并将结果压入栈,直到所有操作符均遍历完。

关于中缀表达式转换成后缀表达式的方式主要有以下两种:1.调度场算法(可参考维基百科),2.2中提到的算法思想是针对二元表达式的一种简化版本。2.将中缀表达式转换成表达式树再进行后序遍历得到后缀表达式。

2.4前缀表达式与后缀表达式也是大同小异,只不过顺序有变,这里不再做分析。


Ps:本文有不少内容借鉴网上发布的博客、文章等,若有侵犯请告知,我会尽快做出合适的处理。

  • 6
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值