算术表达式的计算:中序表达式转后序表达式
中序表达式转后序表达式的目的
在我们的日常数学表达中使用的诸如2*(5-1) 这样的表达式是中序表达式,我们使用数学计算规则对表达式进行计算,但是计算机要计算算术表达式,如果直接使用计算规则(括号-乘除-加减)这样的顺序计算的话就要去使用if-else语句判断括号、加减乘除然后计算,如果一个算术表达式足够长,那么对应程序的if-else就会足够复杂,更重要的是这个程序要能适用于所有的算术表达式,使用简单的if-else是不可能完成这个功能的。
为了解决这个问题,使用后序表达式,所谓后序表达式就是数字在前,op(+、-、*、/)在后,下面的列表列出几种中序表达式和对应的后序表达式,每个计算元素之间使用空格。
中序表达式 | 后序表达式 |
---|---|
2 * ( 5 - 1 ) | 2 5 1 - * |
5 + 60 * ( 3 - 1 ) / ( 6 - 4 ) + 3 | 5 60 3 1 - * 6 4 - / 3 + + |
转换为后序表达式之后就可以使用单一的计算规则对任何后序表达式进行计算:从前往后,遇到op(+、-、*、/) 就对该符号之前的两个数值进行相应计算,直到最后一个op(+、-、*、/) 的计算执行完毕。例如计算2 5 1 - * 时先遇到 - ,则先计算5-1得到4,再遇到 *,计算 2*4,这样后序表达式的值就被计算出来了。从前面的计算结果的描述上能看到,遇到计算符号就进行计算符号之前的两个数字的计算,从后往前,这种场景很适合Stack(栈)的使用,在后面的代码中就能看到。
由此可见将中序表达式转为后序表达式之后任何表达式都可以用一种规则进行计算,换句话说我们的程序将能够计算任何的算术表达式。
中序表达式转后序表达式
中序表达式转后序表达式的具体规则如下:
- 准备两个栈(java.util.Stack),栈number用来存储数据和后序表达式的结果,栈action用来存储op(+、-、*、/)。
- 数字直接压入number栈,如果action栈为空,则op(+、-、*、/)压栈,如果不为空,遵循 3 中的规则。
- 遇到op(+、-、*、/)时检查此操作符和栈顶操作符的优先级,如果优先级高于栈顶操作符,则此操作符压入action栈,否则将栈顶操作符弹出压入number栈(number栈存储结果),此操作符压栈。
- 如果遇到“(”则直接压入action栈,如果 3 中检查优先级时栈顶的操作符为“(”则认为任何操作符的优先级高于“(”,直接将操作符压栈。
- 如果遇到“)”则action弹栈并压入number栈,直到遇到“(”。
- 中序表达式结束之后如果action栈不为空,则action弹栈并依次压入number栈。
- 所有操作结束后number中存储的就是后序表达式的反序,将number栈中的元素顺序反过来就是后序表达式
下面是中序表达式转后序表达式的例程,中序表达式在cmd窗口输入,以 ctrl+z 结束输入。
import java.util.Scanner;
import java.util.Stack;
public class Evaluate {
public static void main(String[] args) {
Stack<String> postfix = toPostfix();
System.out.println(postfix);
}
/**
* 进行转后序表达式的主程序,由flag()方法进行各种情况的判断,此方法处理各种情况的实现逻辑。
* @param string 中序表达式
* @return 存储后序表达式的Stack
*/
public static Stack<String> toPostfix() {
Stack<String> number = new Stack<String>();
Stack<String> action = new Stack<String>();
Scanner scanner=new Scanner(System.in);
scanner.useDelimiter("\\s");
int symble = 0;
while(scanner.hasNext()) {
String s = scanner.next();//读取输入
symble = flag(s, number, action);
switch (symble) {
case 1:
number.push(s);
break;
case 2:
action.push(s);
break;
case 3:
action.push(s);
break;
case 4:
number.push(action.pop());//action弹栈并压入number
action.push(s);//操作符压栈
break;
case 5:
action.push(s);
break;
case 6:
first: while (!action.isEmpty()) {//action弹栈并压入number栈直到遇到左括号
String temp = action.pop();
if (temp.equals("(")) {
break first;
} else {
number.push(temp);
}
}
break;
default:
break;
}
}
Stack<String> temp = new Stack<String>();
for (String string2 : action) {
number.push(string2);// 数字处理结束之后将action中的操作符压入number栈
}
for (String string3 : number) {
temp.push(string3);// 反序
}
return temp;
}
/**
* 中序转后序表达式的各种逻辑判断,将判断的结果送入toPostfix()进行各种情况的具体逻辑处理
*
* @param s 中序表达式
* @param number number栈
* @param action action栈
* @return 返回各种情况的symbol
*/
public static int flag(String s, Stack<String> number, Stack<String> action) {
if (s.matches("\\d+"))
return 1;// number
if (s.matches("(\\+)|(\\-)|(\\*)|(\\/)")) {
if (action.isEmpty()) {
return 2;// action为空
} else if (prior(s, action.peek())) {
return 3;// action不为空,操作符优先级高于栈顶操作符
} else {
return 4;// action不为空,操作符优先级不高于栈顶操作符
}
}
if (s.matches("\\("))
return 5;// 左括号
if (s.matches("\\)"))
return 6;// 右括号
return 0;
}
/**
* 判断操作符和栈顶操作符的优先级
* @param s1 操作符
* @param s2 栈顶操作符
* @return 优先级
*/
public static Boolean prior(String s1, String s2) {
if (s2.matches("\\(")) //任何操作符的优先级高于左括号
return true;
if (s1.matches("(\\*)|(\\/)") && s2.matches("(\\+)|(\\-)"))
return true;
return false;
}
}
运行结果如下:
后序表达式的计算
后序表达式的计算规则在前面已经讲过了,需要注意的时Stack弹栈的顺序时后进先出LIFO,所以特别注意如果遇到(-、/)的时候的计算顺序。
/**
* 计算后序表达式的值
* @param stack 存储后序表达式的栈
* @return 计算结果
*/
public static Double evaluatePostfix(Stack<String> stack) {
Stack<String> newStack = new Stack<String>();
for (String string : stack) {
if (string.matches("\\d+"))
newStack.push(string);
else if (string.matches("(\\+)|(\\-)|(\\*)|(\\/)")) {
if (newStack.size() < 2) {//遇到操作符时栈内至少应该有两个元素
break;
}
double a = Double.parseDouble(newStack.pop());
double b = Double.parseDouble(newStack.pop());//弹出将要进行计算的数值
switch (string) {
case "+":
newStack.push(String.valueOf(b + a));
break;
case "-":
newStack.push(String.valueOf(b - a));//计算结果并压栈,注意顺序
break;
case "*":
newStack.push(String.valueOf(b * a));
break;
case "/":
newStack.push(String.valueOf(b / a));
break;
default:
break;
}
}
}
return Double.parseDouble(newStack.pop());
}
在main()方法中加入System.out.println(evaluatePostfix(postfix));
,计算结果如下: