文章目录
- 前言
- 一、后缀表达法
- 二、把中缀表达式转换成后缀表达式
- 1. 人类是如何计算中缀表达式的值
- 2.如何将中缀表达式转换成后缀表达式
- 三、转换规则
- 1. 中缀表达式转成后缀表达式的java代码
- 2. 后缀表达式求值过程
- 3. 后缀表达式的求值规则
- 4. 后缀表达式求值的java代码
- 总结
前言
前面两篇文章分析了栈的基本使用,最后我们使用栈来解析算术表达式。如 4+6,或者5*(2+3),或者((2+4)7)+3*(8-4))等。
计算机的算法直接求算术表达式的值,还是比较困难的。因此分两步实现算法:
1. 将算术表达式转化成另一种形式:后缀表达式。
2. 计算后缀表达式的值。
一、后缀表达法
日常算术表达式的将操作符(+,-,*,/)放在两个操作数之间。所以把这种写法叫做中缀表达法。例如日常我们写的形如3+3,5/7或者使用字母A+B,A/B等。
后缀表达式,由一位波兰的数学家发明,操作符跟在两个操作数的后面。这样,A+B就称为AB+,A/B成为AB/
中缀表达式和后缀表达式相互转换
中缀表达式 | 后缀表达式 |
A+B-C | AB+C- |
A*B/C | AB*C/ |
A+B*C | ABC*+ |
A*B+C | AB*C+ |
A*(B+C) | ABC+* |
AB+CD | ABCD+ |
(A+B)*(C-D) | AB+CD-* |
((A+B)*C)-D | AB+C*D- |
A+B*(C-D/(E+F)) | ABCDEF+/-*+ |
二、把中缀表达式转换成后缀表达式
下面内容解释中缀表达式的算术表达式是如何转换成后缀表达式。为了理解怎样创建后缀表达式,先看怎么计算后缀表达式的值。如2*(3+4)的后缀表达式234+*是怎样求出结果14的。(为了简单起见,本次讨论的操作数都是一位数字)
1. 人类是如何计算中缀表达式的值
虽然对计算机来说求值很难,对于人类来说计算出3+5+4或者3*(4+5)的值不难。通过分析人类是如何计算这些表达式的值,可以得到一些把表达式转换成后缀表达式的启发。
总的来说,人类解算术表达式的时候,应该遵循以下几条规则:
- 从左到右读取算式。
- 已经读到了可以计算值的两个操作数和一个操作符时,就可以计算,并用计算结果代替那两个操作数和那个操作符。
- 继续这个过程。从左到右,能算就算,直到表达式的结尾。
下面来看三个非常简单的中缀表达式求值的例子。
求值3+4-5
读取元素 | 解析后的表达式 | 说明 |
3 | 3 | |
+ | 3+ | |
4 | 3+4 | |
- | 7 | 看到-后,可计算3+4 |
7- | ||
5 | 7-5 | |
END | 2 | 表达式末端后,可计算7-5 |
直到看到4后面的操作符才可以计算3+4,如果后面操作符时*或/,就要在乘除运算完成之后,才能做加法操作。
但是在这个例子中4后面的操作符是‘-’,它和加法运算的院线及相同,所以看到‘-’后,既可以计算3+4了,得到7,代替3+4.到表达式末端既可以计算7-5了。
求值3+4*5
读取元素 | 解析后的表达式 | 说明 |
3 | 3 | |
+ | 3+ | |
4 | 3+4 | |
* | 3+4* | 不能计算3+4,因为*比+优先级更高 |
看到5时,就可以计算4*5 | ||
5 | 3+4*5 | |
3+20 | ||
END | 23 | 到达表达式末端时,可计算3+20 |
因为乘,除有比加,减更高的优先级所以要等计算出4*5的结果后才能做加3的运算。
有一点需要注意,在执行乘法运算之后还是不能直接执行加法运算,当发现5后面什么也没有了,他就是表达式的结尾了。才可以执行加法运算。
括号用于覆盖操作符原有的优先级。下面看3*(4+5)的解析过程
读取元素 | 解析后的表达式 | 说明 |
3 | 3 | |
* | 3* | |
( | 3*( | |
4 | 3*(4 | 由于括号,所以不能计算3*4 |
+ | 3*(4+ | |
5 | 3*(4+5 | 仍不能计算4+5 |
) | 3*(4+5) | 看到)后,就可以计算4+5了 |
3*9 | 看完4+5后,可计算3*9 | |
27 | ||
END | 完毕 |
通过上面三个例子,计算中缀表达式时,既要向前,又要向后读表达式。向前(从左到右)读操作数和操作符。等到读到足够的信息来执行一个运算时,向后去找出两个操作数和操作符,执行运算。
有时如果后面是更高级别的操作符或者括号时,就必须推迟此运算。这种情况下,必须先执行后面的级别更高的运算;然后再回头(向左)来执行前面的运算。
虽然我们可以直接编写这样的求值算法,但是,先把表达式转换成后缀表达法计算会更容易。
2.如何将中缀表达式转换成后缀表达式
将中缀表达式转换成后缀表达式的规则和计算中缀表达式的规则类似。但是,将中缀表达式转换成后缀表达式不用做算术运算。只是把操作数和操作符重新排列成另一种形式:后缀表达法。
中缀表达法转后缀表达法规则如下
1. 如果中缀字符串中的字符是操作数,则立即把它复制到后缀字符串中。也就是说,如果看到中缀字符串中的A时,就立即把A写到后缀字符串里。
2. 一旦可以利用操作符求中缀表达式的某部分的值,就把该运算符复制到后缀字符串中。
将A+B-C转换成后缀表达式
从中缀比爱哦大师中读取字符串 | 分解中缀表达式过程 | 求后缀表达式过程 | 说明 |
A | A | A | |
+ | A+ | A | |
B | A+B | AB | |
- | A+B- | AB+ | 读到-,可以把+复制到后缀字符串中 |
C | A+B-C | AB+C | |
END | A+B-C | AB+C- | 当读到表达式结尾处,可以复制- |
每当计算求值的时候,只需要把操作符写到后缀表达式的输出中。
将A+B*C转换成后缀表达式
从中缀表达式中读取字符串 | 分解中缀表达式过程 | 求后缀表达式过程 | 说明 |
A | A | A | |
+ | A+ | A | |
B | A+B | AB | |
* | A+B* | AB | |
C | A+B*C | ABC | 看到C后可以复制* |
A+B*C | ABC* | ||
END | A+B*C | ABC*+ | 看到表达式末端时,可以复制+ |
将A*(B+C)转换成后缀表达式
从中缀表达式中读取字符串 | 分解中缀表达式过程 | 求后缀表达式过程 | 说明 |
A | A | A | |
* | A* | A | |
( | A*( | A | |
B | A*(B | AB | 因为有括号,所以不能复制* |
A*(B+ | AB | ||
+ | A*(B+C | ABC | 不能复制+ |
C | A*(B+C) | ABC+ | 看到)时,可以复制+ |
) | A*(B+C) | ABC+* | 复制完+后,可以复制* |
END | A*(B+C) | ABC+* | 完毕 |
在转换过程中,需要向前和向后两个防线来扫描中缀表达式,以完成后缀表达式的转换。当某个操作符后面的操作符优先级更高或者为括号时,必须要比底优先级的操作符更早的写到后缀表达式字符串中。
A+BC和A(B+C)转换到后缀表达式的过程中,操作符的顺序是颠倒的。因为第一个操作符必须等第二个操作符输出后才能输出,所以操作符在后缀字符串中的顺序和中缀字符串中的顺序是相反的。下面再举一个较长的表达式可以更清楚的说明这一点。
这里先加入栈的内容,稍后再解释。
将A+B*(C-D)转换成后缀表达式
从中缀表达式中读取字符串 | 分解中缀表达式过程 | 求后缀表达式过程 | 栈中的内容 |
A | A | A | |
+ | A+ | A | + |
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-*+ |
可以看出,操作符的初始顺序在中中缀表达式是+-,但是在后缀表达式中却是-+。者是因为’*‘比’+‘的优先级更高,而括号‘-’在括号中所以优先级比’ * '高。
这种颠倒的顺序用栈来存储操作符时一个很好的方法。
三、转换规则
下面使用一个表格总结转换规则。在表中‘>’和‘>=’符号表示操作符之间的优先级关系,而不是数值比较关系。opThis是中缀表达式中读到的操作符。而opTop是刚刚出栈的操作符。
从输入中缀表达式中读取的字符串 | 动作 |
操作数 | 输出到后缀表达式字符串中 |
左括号( | 压入栈中 |
右括号) | 栈非空时,重复一下步骤 |
弹出一项, | |
若该项不为(,则写到至输出中 | |
该项为(则退出循环 | |
Operator(opThis) | 若栈为空 |
把opThis压入栈中 | |
否则, | |
栈非空时,重复 | |
弹出一项 | |
若项为(,压其入栈,或 | |
若项为operator(opTop),且 | |
若opTop <opThis,把opTop压入栈,或 | |
若opTop >=opThis,输出opTop | |
若opTop <opThis则退出循环,或项为( | |
把opThis压入栈 | |
没有更多项 | 当栈为空时 |
弹出项目,将其输出 |
利用上述规则,将其他一些简单的中缀表达式转换为后缀表达式,类似的表格如下:
转换规则应用于A+B-C
从中缀表达式中读取字符串 | 分解中缀表达式过程 | 求后缀表达式过程 | 栈中的内容 | 规则 |
A | A | A | 将操作数写至后缀表达式字符串 | |
+ | A + | A | + | 若栈为空,把opThis压入栈 |
B | A+B | AB | + | 将操作数 写至后缀表达式字符串 |
- | A+B- | AB | 若栈非空,所以弹出项 | |
A+B- | AB+ | opThis为-,opTop为+ | ||
opTop>=opThis,所以输出opTop | ||||
A+B- | AB+ | - | 然后把opThis压入栈 | |
C | A+B-C | AB+C | - | 将操作数写至后缀表达式字符串 |
END | A+B-C | AB+C- | 弹出剩余项,将其输出 |
转换规则应用于A+B*C
从中缀表达式中读取字符串 | 分解中缀表达式过程 | 求后缀表达式过程 | 栈中的内容 | 规则 |
A | A | A | 将操作数写至后缀表达式字符串 | |
+ | A+ | A | + | 若栈为空,把opThis压入栈 |
B | A+B | AB | + | 将操作数写至后缀表达式字符串 |
* | A+B* | AB | 栈非空,所以弹出opTop | |
A+B* | AB | + | opThis为*,opTop为+, | |
opTop<opThis,所以把opTop压入栈 | ||||
A+B* | AB | +* | 然后把opThis压入栈 | |
C | A+B*C | ABC | +* | 将操作数写至后缀表达式字符串 |
END | A+B*C | ABC* | + | 弹出剩余项,将其输出 |
A+B*C | ABC*+ | 弹出剩余项,将其输出 |
转换规则应用于A*(B+C)
从中缀表达式中读取字符串 | 分解中缀表达式过程 | 求后缀表达式过程 | 栈中的内容 | 规则 |
A | A | A | 将操作数写至后缀表达式字符串 | |
* | A* | A | * | 若栈为空,把opThis压入栈 |
( | A*( | A | *( | 将(压入栈 |
B | A*(B | AB | *( | 将操作数写至后缀表达式字符串 |
+ | A*(B+ | AB | * | 若栈非空,弹出项 |
A*(B+ | AB | *( | 有(,所以压入栈中 | |
A*(B+ | AB | *(+ | 然后把opThis压入栈 | |
C | A*(B+C | ABC | *(+ | 将操作数写至后缀表达式字符串 |
) | A*(B+C) | ABC+ | *( | 弹出项,写至后缀表达式字符串 |
A*(B+C) | ABC+ | * | 若有(则退出 | |
END | A*(B+C) | ABC+* | 弹出剩余项,写至后缀表达式字符串 |
1. 中缀表达式转成后缀表达式的java代码
public class InToPost {
private Stack<Character> stack;
private String input;
private String output = "";
public InToPost(String input) {
this.stack = new Stack();
this.input = input;
}
public String doTrans() {
for (int i = 0; i < input.toCharArray().length; i++) {
char ch = input.charAt(i);
System.out.println("For "+ch+" "+stack);
switch (ch) {
case '+':
case '-':
getOper(ch , 1);
break;
case '*':
case '/':
getOper(ch ,2);
break;
case '(':
stack.push(ch);
break;
case ')':
//遇到右括号,把括号中的操作符,添加到后缀表达式字符串中。
getParen(ch);
break;
default:
output = output + ch;
break;
}
}
while (!stack.isEmpty()){
System.out.println("While "+Arrays.asList(stack));
output = output + stack.pop();
}
System.out.println("End "+Arrays.asList(stack));
return output;
}
/**
* 从input获得操作符
*
* @param opThis
* @param currentPriority 操作符的优先级
*/
private void getOper(char opThis, int currentPriority) {
while (!stack.isEmpty()) {
char opTop = stack.pop();
//括号有较高优先级重新压入栈中
if (opTop == '(') {
stack.push(opTop);
break;
} else {
int stackTopPriority;
//+ ,-优先级都是1
if (opTop == '+' || opTop == '-') {
stackTopPriority = 1;
} else {
stackTopPriority = 2;
}
//如果当前优先级大于栈顶部的优先级,重新压入栈中,否则出栈加入到后缀表达式字符串中
if (stackTopPriority<currentPriority){
stack.push(opTop);
break;
}else {
output = output +opTop;
}
}
}
stack.push(opThis);
}
public void getParen(char ch){
while (!stack.isEmpty()){
char chx = stack.pop();
//如果是'('直接返回,其他操作符直接拼接到后缀表达式中。
if (chx == '('){
break;
}else {
output = output +chx;
}
}
}
}
2. 后缀表达式求值过程
从右边第一个操作符开始,把它和它左边相邻的操作数画在同一个园里。然后就应用操作符运算两个操作数求值。
3. 后缀表达式的求值规则
从上述图可以看出,没遇到一个操作符,就用它来运算在这之前的操作数。这表明可以利用栈来存储操作数。(和中缀表达式转后缀表达式相反,那是把操作符存储在栈里)
后缀表达式求值
从后缀表达式中读取的元素 | 执行的动作 |
操作数 | 入栈 |
操作符 | 从栈中提出两个操作数,用操作符执行运算。结果入栈 |
全部做完之后就可以得到答案了。
4. 后缀表达式求值的java代码
public class ParsePost {
private Stack<Integer> stack;
private String input;
public ParsePost(String input) {
this.input = input;
}
public int doParse(){
stack = new Stack<>();
char ch;
int num1 , num2 ,interAns;
for (int i = 0; i < input.length(); i++) {
ch = input.charAt(i);
System.out.println(" " + ch + " " + stack);
if (ch >='0' && ch<='9'){
stack.push( ch-'0');
}else {
num2 = stack.pop();
num1 = stack.pop();
switch (ch){
case '+':
interAns = num1 + num2;
break;
case '-':
interAns=num1 -num2;
break;
case '*':
interAns = num1 * num2;
break;
case '/':
interAns = num1/num2;
break;
default:
interAns=0;
break;
}
stack.push(interAns);
}
}
interAns = stack.pop();
return interAns;
}
}
public class Main {
public static void main(String[] args) {
String s = "3*(4+5)-6/(1+2)";
InToPost inToPost = new InToPost(s);
String trans = inToPost.doTrans();
System.out.println(trans);
ParsePost parsePost = new ParsePost(trans);
int output = parsePost.doParse();
System.out.println(output);
}
执行结果
For 3 []
For * []
For ( [*]
For 4 [*, (]
For + [*, (]
For 5 [*, (, +]
For ) [*, (, +]
For - [*]
For 6 [-]
For / [-]
For ( [-, /]
For 1 [-, /, (]
For + [-, /, (]
For 2 [-, /, (, +]
For ) [-, /, (, +]
While [[-, /]]
While [[-]]
End [[]]
345+*612+/-
3 []
4 [3]
5 [3, 4]
+ [3, 4, 5]
* [3, 9]
6 [27]
1 [27, 6]
2 [27, 6, 1]
+ [27, 6, 1, 2]
/ [27, 6, 3]
- [27, 2]
25
总结
可以看到,中缀表达式转换为后缀表达式之后解析变得非常简单。至此java解析算术表达式就分析完了。