栈:装水的杯子(三)后缀表达式

rel="File-List" href="file:///C:%5CDOCUME%7E1%5CADMINI%7E1%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">

(三)后缀表达式

爸爸新买了一个全新的科学计算器,功能非常强大,相比之下子渊的那个玩具一样的简易计算器就逊色多了。最让子渊惊奇的是科学计算器能够一次性接受一连串的表达式,然后飞快地给出结果,而他的简易计算器却每次只能接受一个操作数或者一个操作符,麻烦死了。

比如计算 200805 + 30 * 350,用简易计算器只能先输入操作数“30”,然后输入操作符“*”,再输入操作数“350”,得到中间运算结果“10500”,再输入操作符“+”,最后输入操作数“200805”,点击“=”,才能得到最终结果“211305”。

而科学计算器却只要直接输入“200805 + 30 * 350”,然后点击“=”,就能得到最终结果“211305”。

简直太神奇了!它是怎么做到的呢?

子渊带着这个问题来到爸爸的书房。爸爸正在下围棋忙得不亦乐乎,听子渊说完以后,头也没回,只丢下一句话:“不好意思啊我现在没空!再等两分钟吧。顺便说一句,这个问题用我昨天跟你讲的数据类型栈可以很好的解决,你自己先想想看吧。呵呵,杀你大龙!”

用栈解决?怎么看也搭不上边啊!子渊陷入沉思中。

“终于赢了!”两分钟后,爸爸大吼一声,满脸兴奋地转过身来,“想出来没有啊?”

“一点思路也没有,简直是乱说嘛!”

“乱说?我可没有乱说啊!哦,可能跨度有点大,你先解决这道题目吧。”爸爸从他的习题库里调出了一道题目:

标准的表达式如"A+B",在数学上学名叫中缀表达式,原因是运算符号在两个运算对象的中间。相对应的还有前缀表达式,如:"+ - A * B C D",转换成中缀表达式为:"A - B * C + D";后缀表达式,比如前所述的中缀表达式转换为后缀表达式为:"A B C * - D +"。为了纪念波兰数学家鲁卡谢维奇(Jan Lukasiewicz),前缀表达式被称作波兰表达式,后缀表达式称为逆波兰表达式。

逆波兰表达式是一种把运算符后置的算术表达式,例如普通的表达式2 + 3的逆波兰表示法为2 3 +。逆波兰表达式的优点是运算符之间不必有优先级关系,也不必用括号改变运算次序,运算符放在两个运算对象之后,所有计算按运算符出现的顺序,严格地由左往右进行。例如5 * (7 – 3) + 9对应的逆波兰表达式为5@7@3@ - * 9@ + #,其中#为逆波兰表达式的结束标志,@为操作数的结束符。

计算过程如下:

5@7@3@ - * 9@ +

= 5@4@ * 9@ +

= 20@ 9@ +

= 29

本题要求计算逆波兰表达式的值,为简单起见,假设操作数为正整数,运算符只包括+ - * / 四个。(有创造力的读者可以自己提升难度:拓宽操作数的类型,增加运算符的总类)

设计一个算法来实现该功能,子函数接口为:

FUNCTION ComputerReversePolishNotation(rpn : string) : integer;

 

子渊一看,发现这道题目简单无比,只要一个入栈和出栈就可以搞定。由左往右处理字符串的每一个字符,若遇到操作数就保存到栈中;若遇到操作符就从栈中弹出栈顶的两个操作数进行计算,然后将结果重新压入栈中。依此类推,直到表达式最后一个操作符处理完毕,这时的栈顶元素值即为计算结果。

子渊稍作分析,便得知了代码的写法,但是他想:每次都是爸爸考我,也不知道他自己水平如何,不如就用这道题目考考他吧,呵呵!

眉头一皱,计上心来:“爸爸,你每次都是给我提示,从来没有在我面前写过完整的代码,不如这次我们每人都写一个,比比看谁的好吧?”

“小家伙,想考我啊!那就让你见识见识。好好看着哦!”爸爸心想这道题目确实不难,就从另外的角度让子渊学习学习编写代码的方法吧,于是满口答应。

不到五分钟,代码编辑,编译,运行成功:

{代码6}

{计算逆波兰表达式的值}

PROGRAM ReversePolishNotation(INPUT, OUTPUT);

CONST

    MAXCAPACITY = 255; {栈的最大容量}

 

TYPE

    ElemType = integer;  {栈内元素数据类型}

    Stack    = array [1..MAXCAPACITY] of ElemType; {用数组表示的栈}

  

VAR

    rpn  : string;

    s    : Stack;       {定义s为栈}

    top  : integer;     {栈顶标志}

 

PROCEDURE Push(var s : Stack; var top : integer; data : ElemType); {入栈}

begin

    if top = MAXCAPACITY then

        writeln('overflow')

    else

    begin

        inc(top);

        s[top] := data;

    end; {if}

end; {Push}

   

FUNCTION PopTop(s : Stack; var top : integer)  : ElemType; {获取栈顶元素的值并退栈}

begin

    if top = 0 then

        writeln('underflow')

    else

    begin

        PopTop := s[top];

        dec(top);

    end; {else}

end; {PopTop}

   

FUNCTION ComputerReversePolishNotation(rpn : string) : integer;

var

    i, value : integer;

begin

    i := 1;

    while rpn[i] <> '#' do

    begin

        case rpn[i] of

            '0'..'9' : begin

                           value := 0;

                           repeat {从后缀表达式中取出操作数}

                               value := 10 * value + ord(rpn[i]) - ord('0');

                               inc(i);

                           until rpn[i] = '@';

                           Push(s, top, value); {操作数入栈}

                       end; {'0'..'9'}

          '+' : Push(s, top, PopTop(s, top)+PopTop(s, top)); {弹出两个操作数并做加法运算}

          '-' : begin

                    value := PopTop(s, top); {弹出减数}

                    Push(s, top, PopTop(s, top)-value); {弹出被减数并做减法运算}

                end; {'-'}

          '*' : Push(s, top, PopTop(s, top)*PopTop(s, top)); {弹出两个操作数并做乘法运算}

          '/' : begin

                    value := PopTop(s, top); {弹出除数}

                    if value = 0 then {预防除数为0的情况}

                    begin

                        writeln('divisor = 0');

                        exit;

                    end; {if}

                    Push(s, top, PopTop(s, top) div value); {弹出被除数并做除法运算}

                end; {'/'}

        end; {case}

        inc(i);

    end; {while}

       

    ComputerReversePolishNotation := PopTop(s, top); {返回运算结果}

end; {ComputerReversePolishNotation}

   

BEGIN {MAIN}

    top := 0; {栈顶初始化}

    

    writeln('Input rpn:');

    readln(rpn);

 

    writeln(ComputerReversePolishNotation(rpn));

END.

 

子渊一看代码,觉得有些不对劲:“不对啊!爸爸,你的代码里怎么只有一个入栈基本运算啊?还有那个PopTop函数是怎么回事?”

“傻眼了吧!告诉你:前面的题目中我们直接调用栈的基本操作,是为了让你熟悉栈的特点。但是,我们学算法不应拘泥于形式,而要领会其精神,灵活运用栈的特点编写更加短小精悍的代码。

上面的代码虽然没有明显地写出栈的各种基本操作,但实际上已经暗含在各个语句中了,PopTop函数实际上是PopGetTop函数的综合。

实际上,在以后的代码中,也许你根本看不到PushPopGetTop的字样,但是,只要题目中有可以表示栈的数组或链表,有后进先出的特征呈现,它就是栈。

栈的应用非常广阔,在以后学习的深度优先搜索,回溯等知识时,栈还要充当重要的主角,希望你能够掌握栈的精髓,而不仅仅是几个函数。

接下来是与上一题密切相关的一个题目,解决了它,科学计算器的奥秘就不神秘了。如何灵活地运用栈,使代码更简洁,美观,你要好好想一想:

将一个普通的中序表达式转换为逆波兰表达式。例如中序表达式5 * (7 – 3) + 9对应的逆波兰表达式为5@7@3@ - * 9@ + #,其中#为逆波兰表达式的结束标志,@为操作数的结束符。

为简单起见,假设操作数为正整数,只出现小括号,并且运算符只包括+ - * / 四个。(有创造力的读者可以自己提升难度:拓宽操作数的类型,增加运算符的总类)

子过程接口为:

PROCEDURE CreateReversePolishNotation(var rpn : string; infix : string);

 

逆波兰表达式把运算符后置,也就是说我先要把运算符后面的操作数读出来,再去处理运算符,这样就需要先把运算符存储起来。那存到哪里呢?当然是栈中了。

逆波兰表达式没有括号和优先级关系,那就完全由运算符的出栈顺序来决定计算顺序,优先级别越高的运算符越先出栈。

那在运算符入栈时就要考虑栈顶运算符的优先级别情况:如果栈顶运算符优先级低于该运算符,则将该运算符入栈;否则将栈顶运算符从栈中弹出,直到栈顶运算符的优先级低于当前运算符为止,将该运算符入栈(左右括号是特例)。

在本题中,优先级别的设定是大家都熟知的:’*’’/’最高;其次为’+’’-‘;再次为左,右括号’(‘’)’ 最后为逆波兰表达式的结束标志’#’

有了这些算法分析,答案就呼之欲出了:

{代码7}

{将中序表达式转换为逆波兰表达式}

PROGRAM ReversePolishNotation(INPUT, OUTPUT);

VAR

    rpn, infixNotation : string; {前缀,后缀表达式}

 

{返回各种操作符的优先级别}

FUNCTION Priority(ch : char) : integer;

begin

    case ch of

        '#'     : Priority := 0;

        '(',')' : Priority := 1;

        '+','-' : Priority := 2;

        '*','/' : Priority := 3;

    end; {case}

end; {Priority}

 

PROCEDURE CreateReversePolishNotation(var rpn : string; infix : string);

var

    s   : array [1..255] of char; {定义s为栈}

    top : integer;         {栈顶标志}

    i, len : integer;

begin

    top := 1; {栈顶初始化}

    s[top] := '#';

 

    len := length(infix);

    for i:=1 to len do

    begin

        if infix[i] in ['(',')','+','-','*','/'] then

        begin

            if infix[i-1] in ['0'..'9'] then {操作数结束标志}   

                rpn := rpn + '@';

         

            if (infix[i] = '(') or (Priority(infix[i]) > Priority(s[top])) then {入栈}

            begin

                inc(top);

                s[top] := infix[i];

            end   {if}

            else if infix[i] = ')' then

            begin

                while s[top] <> '(' do {退栈,直到遇到左括号为止}

                begin

                    rpn := rpn + s[top];

                    dec(top);

                end; {while}

                dec(top); {左括号出栈}

            end {else if}

            else {栈顶运算符的优先级不低于当前运算符,先退栈,再入栈}

            begin

                while Priority(infix[i]) <= Priority(s[top]) do

                begin

                    rpn := rpn + s[top];

                    dec(top);

                end; {while}

                inc(top);

                s[top] := infix[i];

            end; {else}

        end  {if}

        else {读入操作数}

            rpn := rpn + infix[i];

    end; {for}

 

    if infix[len] in ['0'..'9'] then {操作数结束标志}   

        rpn := rpn + '@';

   

    while top > 0 do  {将栈内所有元素弹出}

    begin

        rpn := rpn + s[top];

        dec(top);

    end; {while}

end; {CreateReversePolishNotation}

   

BEGIN {MAIN}

    writeln('Input infixNotation:');

    readln(infixNotation);

 

    CreateReversePolishNotation(rpn, infixNotation);

    writeln(rpn);

END.

看了子渊的代码,爸爸忍不住笑了起来:“小家伙,领悟挺快的啊,对栈的基本思想已经有了一定的认识了!好了,把上述两个子函数结合起来,去实现你的混合运算吧。”

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值