4.4.3 后缀表达式求值
后缀表达式最大的优点是不需要括号来指定运算的优先级,其运算符的出现顺序完全决定了计算的顺序。求值过程使用一个栈(操作数栈)来临时存储操作数和中间计算结果。详细步骤如下:
- 创建一个空栈(通常称为
stack
或操作数栈
),用于存放操作数。 - 从左到右依次扫描后缀表达式
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)操作数栈。
- 从操作数栈中连续出栈(Pop)两个操作数。
- 当元素是操作数:因为操作数是运算的基础,需要先保存起来,等待其对应的运算符出现后再进行计算,所以,将其转换为数值(例如,从字符串
- 重复步骤 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 + b
和 b + 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
【解】
入栈元素 | 栈内(→栈顶) | 操作 | 队内(→队尾) |
---|---|---|---|
e1 | e1 | ||
e2 | e1, e2 | e2出栈,入队列 | e2 |
e3 | e1, e3 | ||
e4 | e1, e3, e4 | e4, e3 依次出栈,入队列 | e2, e4, e3 |
e5 | e1, e5 | ||
e6 | e1, e5, e6 | 三个元素依次出栈,再依次入队 | e2, e4, e3, e6, e5, e1 |
故,栈的容量至少是 3。
本题答案:B