中缀表达式的求值
中缀表达式的求值可以通过将中缀表达式先转为后缀表达式,再对后缀表达式求值实现。
因此,我们介绍中缀表达式转后缀表达式与对后缀表达式求值两个步骤,只要将这两个步骤合并就是对中缀表达式求值的步骤了。
中缀表达式转后缀表达式
中缀表达式是我们习惯的书写形式,如 a+b-a*((c+d)/e-f)+g
,但更利于计算机求值的是后缀表达式,因为后缀表达式将运算顺序隐藏在了运算符的排列次序中,只要从前往后依次扫描即可完成计算。因此,中缀表达式转换为后缀表达式在实际应用中具有重大意义。
假定所有的数字是由26个大小写字母代表的,运算分为加减乘除,允许用小括号改变优先级。我们可以设计相应的算法将符合以上规则的中缀表达式转后缀表达式。
首先,我们定义操作符的栈内优先级和栈外优先级(我也不知道栈外优先级有什么用,不过书上就是这样写的),默认情况为-1是为了便于判定出错
栈内优先级
操作符 | # | ( | * | / | + | - | ) | 其他 |
---|---|---|---|---|---|---|---|---|
优先级 | 0 | 1 | 5 | 5 | 3 | 3 | 6 | -1 |
栈外优先级
操作符 | # | ( | * | / | + | - | ) | 其他 |
---|---|---|---|---|---|---|---|---|
优先级 | 0 | 6 | 4 | 4 | 2 | 2 | 1 | -1 |
对于扫描到的每一个符号,按照如下步骤执行操作:
- 如果扫描到
左括号
,直接入栈;如果扫描到右括号
,将栈内操作符出栈并加入后缀表达式,直到遇到左括号为止,左括号出栈但不加入后缀表达式 - 如果扫描到
操作数
,直接加入后缀表达式 - 如果扫描到
运算符
,依次将栈内优先级大于等于当前运算符的符号出栈并加入后缀表达式,直到遇到左括号(左括号不出栈)或者栈空为止。
扫描完毕后,将栈中剩余的符号全部加入后缀表达式。
文末代码的inorderToPostOrderExpression(String inorderExpression)
演示了这一过程,我们输入前面提到的a+b-a*((c+d)/e-f)+g
,可以得到ab+acd+e/f-*-g+
,与笔算结果一致。
理论上,只要栈内优先级的内容足够多,我们就能将更复杂的表达式正确转换。
后缀表达式求值
相比表达式转换,后缀表达式求值显得更为简单,具体方法是不断将操作数入栈,当遇上运算符op时,我们将栈顶两个操作数从栈中弹出,分别记为op2,op1,执行运算op1 op op2
,注意顺序不能颠倒,并将运算结果入栈,不断重复这一过程,直到表达式扫描完毕,此时栈中应该只有一个操作数,此数就是我们要的运算结果。
文末代码演示了这一过程,在中缀转后缀的过程中,代码会给用户输入的字母随机赋值,并在后缀表达式求值的过程中使用,经过多次笔算验证,转换和求值过程均无问题。
代码实现
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Stack;
public class tmp {
private static Map<Character, Integer> charVal= new HashMap<>();
/**
* 取得当前操作符的栈内优先级
* @param ch
* @return
*/
private static int getInStackPriority(char ch) {
switch(ch) {
case '#':return 0;
case '(':return 1;
case '*':return 5;
case '/':return 5;
case '+':return 3;
case '-':return 3;
case ')':return 6;
default: return -1;
}
}
/**
* 取得当前操作符的栈外优先级
* @param ch
* @return
*/
private static int getOutStackPriority(char ch) {
switch(ch) {
case '#':return 0;
case '(':return 6;
case '*':return 4;
case '/':return 4;
case '+':return 2;
case '-':return 2;
case ')':return 1;
default: return -1;
}
}
private static boolean isWord(char ch) {
if((ch==' ')||('a'<=ch&&'z'<=ch)||('A'<=ch&&'Z'<=ch)) return true;
else return false;
}
//中缀表达式转为后缀表达式
private static String inorderToPostOrderExpression(String inorderExpression) {
Stack<Character> opStack = new Stack<Character>();
StringBuilder postOrderExpression = new StringBuilder();
for(int i=0;i<inorderExpression.length();i++) {
char ch = inorderExpression.charAt(i);
//如果遇到操作数,直接加入后缀表达式
if(isWord(ch)) {System.out.print(ch);postOrderExpression.append(ch);
//附加操作,给这个操作数随机赋值
if(charVal.get(ch)==null) {
charVal.put(ch, (int)(10*Math.random()));
}
}
//如果遇到左括号,直接入栈
if(ch=='(') opStack.push(ch);
//ch 将会是 + - * / )中的一项
else if(getInStackPriority(ch)!=-1) {
//如果ch是其他运算符(不是右括号)
if(ch!=')') {
//将栈中所有优先级大于等于当前运算符的符号全部加入后缀表达式,直到栈空或遇到左括号为止
while(!opStack.isEmpty()&&getInStackPriority(opStack.peek())>=getInStackPriority(ch)) {
if(opStack.peek()=='(') {
//左括号也要出栈
opStack.pop();
break;
}
postOrderExpression.append(opStack.peek());
System.out.print(opStack.pop());
}
//将当前符号入栈
opStack.push(ch);
}else {//如果ch是右括号
//将栈中所有符号依次加入后缀表达式,直到遇到左括号为止
while(!opStack.isEmpty()&&opStack.peek()!='(') {
postOrderExpression.append(opStack.peek());
System.out.print(opStack.pop());
}
//左括号也要出栈
opStack.pop();
}
}
}
while(!opStack.isEmpty()) {
if(opStack.peek()!='(') {
postOrderExpression.append(opStack.peek());
System.out.print(opStack.peek());
}
opStack.pop();
}
return postOrderExpression.toString();
}
/**
* 后缀表达式求值
* @return
*/
private static int postOrderExpressionToVal(String postOrderExpression) {
System.out.println(charVal);
Stack<Integer> exp = new Stack<Integer>();
for(int i=0;i<postOrderExpression.length();i++) {
char ch = postOrderExpression.charAt(i);
if(isWord(ch)) exp.push(charVal.get(ch));
else if(getInStackPriority(ch)!=-1) {
int op2 = exp.pop();
int op1 = exp.pop();
switch(ch) {
case '+':exp.push(op1 + op2);break;
case '-':exp.push(op1 - op2);break;
case '*':exp.push(op1 * op2);break;
case '/':exp.push(op1 / op2);break;
}
}
}
return exp.pop();
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String inorderExpression = scanner.nextLine();
scanner.close();
String postOrderExpression = inorderToPostOrderExpression(inorderExpression);
System.out.println();
System.out.println(postOrderExpressionToVal(postOrderExpression));
}
}