第4章栈和队列:栈的应用——后缀表达式求值

4.4.3 后缀表达式求值

后缀表达式最大的优点是不需要括号来指定运算的优先级,其运算符的出现顺序完全决定了计算的顺序。求值过程使用一个栈(操作数栈)来临时存储操作数和中间计算结果。详细步骤如下:

  1. 创建一个空栈(通常称为stack操作数栈),用于存放操作数。
  2. 从左到右依次扫描后缀表达式postexp的每一个字符。
    • 当元素是操作数:因为操作数是运算的基础,需要先保存起来,等待其对应的运算符出现后再进行计算,所以,将其转换为数值(例如,从字符串'5'转换为整数 5)并压入(Push)操作数栈。
    • 当元素是运算符 op
      • 从操作数栈中连续出栈(Pop)两个操作数。
        • 先弹出的元素记为 a(它是第二个操作数)。
        • 后弹出的元素记为 b(它是第一个操作数)。
        • 特别要注意出栈的顺序,非常重要!先出栈的在右边,后出栈的在左边。
      • 根据运算符 op 计算 b op a 的值。
        • 例如,如果 op+,则计算 b + a
        • 例如,如果 op-,则计算 b - a。(b 是被减数,a 是减数)
        • 例如,如果 op*,则计算 b * a
        • 例如,如果 op/,则计算 b / a。(b 是被除数,a 是除数)
      • 上一步计算结果是一个新的中间操作数,可能会参与后续的运算,故将其压入(Push)操作数栈。
  3. 重复步骤 2,直到整个后缀表达式被扫描完毕,此时,操作数栈中应该只剩下一个元素。这个元素就是整个后缀表达式的最终计算结果。将其弹出即可。

上述步骤中,关键的细节是 b op a,之所以如此,而不是 a op b,这是因为栈的“后进先出”(LIFO)特性。例如,计算中缀表达式 5 - (2 + 3) 对应的后缀表达式 5 2 3 + -,按照上述步骤,计算过程如下:

扫描到的元素动作操作数栈的状态 (栈底 -> 栈顶)说明
5是操作数,压入栈。[5]
2是操作数,压入栈。[5, 2]
3是操作数,压入栈。[5, 2, 3]
+是运算符。弹出栈顶两个元素:a = 3, b = 2。计算 b + a = 2 + 3 = 5。将结果 5 压入栈。[5, 5]注意顺序:先弹出的是第二个操作数3,后弹出的是第一个操作数2
-是运算符。弹出栈顶两个元素:a = 5, b = 5。计算 b - a = 5 - 5 = 0。将结果 0 压入栈。[0]同样是 后弹出的 - 先弹出的
结束扫描完毕,弹出栈顶元素 0 作为结果。[]最终结果为 0

对于加法和乘法,a + bb + a 结果一样,顺序似乎不重要。但对于减法和除法,顺序就极其重要。

例 4.4.5:计算后缀表达式 10 6 9 + * 5 / 的结果。

扫描到的元素动作操作数栈(栈底 -> 栈顶)
10压入[10]
6压入[10, 6]
9压入[10, 6, 9]
+弹出 a=9, b=6。计算 6 + 9 = 15。压入 15[10, 15]
*弹出 a=15, b=10。计算 10 * 15 = 150。压入 150[150]
5压入[150, 5]
/弹出 a=5, b=150。计算 150 / 5 = 30。压入 30[30]
结束弹出结果 30[]

最终结果是 30

假设后缀表达式 postexp 是一个字符串数组,并且运算符仅限于 +, -, *, /,在执行出栈(Pop)或入栈(Push)操作时,自动完成应有的数据类型转换。根据前述后缀表达式求值的步骤,可以写出如下算法描述的伪代码。

【算法描述】

double comp_value(char * postexp){
    double d, a, b, c, e;
    SqStack * Opnd; //操作数栈,顺序栈
    InitStack(Opnd); 
    while(*postexp != '\0'){//postexp字符未扫描完毕,循环
        switch(*postexp){
            case '+':
                Pop(Opnd, a);
                Pop(Opnd, b);
                c = b + a;
                Push(Opnd, c);
                break;
            case '-':
                Pop(Opnd, a);
                Pop(Opnd, b);
                c = b - a;
                Push(Opnd, c);
                break;
            case '*':
                Pop(Opnd, a);
                Pop(Opnd, b);
                c = b * a;
                Push(Opnd, c);
                break;
            case '/':
                Pop(Opnd, a);
                Pop(Opnd, b);
                if(a != 0){
                    c = b / a;
                    Push(Opnd, c);
                    break;
                } else {
                    printf("\n\t零不能做分母\n");
                    exit(0);
                }
                break;
            default:
                d = 0; //将连续的数字字符转换成对应的数值存放到 d
                while(*postexp >= '0' && *postexp <= '9'){
                    d = 10 * d + *postexp - '0';
                    postexp++;
                }
                Push(Opnd, d);
                break;
        }
        postexp++;
    }
    GetTop(Opnd, e);
    DestroyStack(Opnd);
    return e;
}

【算法分析】

  • 时间复杂度:O(n)O(n)O(n)
  • 空间复杂度:O(n)O(n)O(n)

例 4.4.6 设栈 S 和队列 Q 的初始状态为空,元素 e1、e2、e3、e4、e5 和 e6 依次进入栈 S,一个元素出栈后即进入 Q,若 6个元素出队的序列是 e2、e4、e3、e6、e5 和 e1,则栈 S 的容量至少应该是( )。

A. 2\qquad B. 3\qquad C. 4\qquad D. 6

【解】

入栈元素栈内(→栈顶)操作队内(→队尾)
e1e1
e2e1, e2e2出栈,入队列e2
e3e1, e3
e4e1, e3, e4e4, e3 依次出栈,入队列e2, e4, e3
e5e1, e5
e6e1, e5, e6三个元素依次出栈,再依次入队e2, e4, e3, e6, e5, e1

故,栈的容量至少是 3。

本题答案:B

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CS创新实验室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值