在编写编译器时经常需要实现对算术表达式的解析,然而对于计算机的算法来说如果直接求解算术表达式的值,还是相当困难的。因此解析算术表达式经常分步实现:
- 将中缀的算术表达式转换为后缀表达式
- 计算后缀表达式的值
在正式介绍算法的实现之前,先介绍一点有关表达式的基础知识
基础知识
1. 后缀表达式
日常算术表达式是将操作符(operator)(+,-,*,/)放在两个操作数(operands)(数字,或者代表数字的字母)中间的,由于操作符写在操作数中间,所以把这种写法称为中缀表达式.对人类而言,中缀表达式便于理解和阅读.
后缀表达式,又称为波兰逆序表达式(Reverse Polish Notation),它是将操作符放在操作数后面的一种表达式的记录方法,比如A+B
变成AB+
,这是一种便于计算机计算的表达式.
中缀表达式 | 后缀表达式 |
---|---|
A+B-C | AB+C- |
A*B/C | AB*C/ |
A+B*C | ABC*+ |
A+B*(C-D/(E+F)) | ABCDEF+/-*+ |
2. 栈
栈(Stack)是计算机中应用的非常多的一种数据结构,其遵循先进后出的规律,可以用数组或者链表来实现栈,本文中就是使用数组实现一个简单的栈.
中缀表达式转换为后缀表达式
将中缀表达式转换为后缀表达式是解析算术表达式中最重要的一步,本文中通过观察几个示例然后总结出转换规律.
1. 分析
将中缀表达式转换为后缀表达式的规则和计算中缀表达式值的规则相似,不需要做计算,只是把操作数和操作符重新排列为另一种形式:后缀表示法.
从左到右的扫描中缀表达式,遇到操作数直接输出,遇到操作符则按照转换规则入栈或者输出.以将A*(B+C)的转换为例:
读取的字符 | 分解中缀表达式 | 求后缀表达式 | 栈中内容 |
---|---|---|---|
A | A | A | |
+ | A+ | B | + |
B | A+B | AB | + |
* | A+B* | AB | +* |
( | A+B*( | AB | +*( |
C | A+B*(C | ABC | +*( |
- | A+B*(C- | ABC | +*(- |
D | A+B*(C-D | ABCD | +*(- |
) | A+B*(C-D) | ABCD- | +*( |
A+B*(C-D) | ABCD- | +*( | |
A+B*(C-D) | ABCD- | +* | |
A+B*(C-D | ABCD-* | + | |
A+B*(C-D) | ABCD-*+ |
从中可以看出,操作符的初始顺序在中缀表达式中是+*-
,但是在后缀表达式中变成了-*+
,这是因为*
比+
的优先级别高,而-
在括号中所以优先级比*
高.
2. 规律总结
通过观察上面的转换例子,可以一般地总结出中缀表达式转换为后缀表达式的转换规则.
从输入中读取的字符 | 动作 |
---|---|
操作数 | 写至输出(postfix) |
左括号 ( | 入栈 |
右括号 ) | 栈非空时,重复以下步骤:弹出一项,若项不为(,则写至输出,项为(,则退出循环 |
Operator(opThis) | 若栈空, 推opThis;否则,栈非空时,重复:弹出一项,若项为(,推其入栈;或项为operator(opTop),且,若opTop<opThis ,推入opTop,或opTop>=opThis ,输出opTop,若opTop<opThis ,则退出循环,或项为(,推入opThis |
没有更多项 | 当栈非空时,弹出项目,将其输出 |
3. 代码实现
利用java实现以上转换过程,关键如下:
/**
* 中缀表达式转换为后缀表达式
*
* @return 转换结果
*/
public String doTrans() {
for (int j = 0; j < input.length(); j++) {
char ch = input.charAt(j);
theStack.displayStack("For " + ch + " ");
switch (ch) {
case '+':
case '-':
gotOper(ch, 1);
break;
case '*':
case '/':
gotOper(ch, 2);
break;
case '('