计算表达式,中缀表达式转后缀表达式步骤详解以及代码实现(Java)
所谓的前、中、后缀表达式,简单阐述中缀表达式和后缀表达式:
- 中缀表达式:常常人们书写的表达式就是中缀表达式,例如:4*(1 + 2) - 3,就是我们平常所使用表达式,对于人来说,中缀表达式通俗易懂,知道怎样计算这个算式,容易理解计算步骤的优先级,那么对于计算机来说,怎样理解这个算式的优先级呢。(所谓优先级就是先计算小括号里的算式,再计算乘除后加减)
- 后缀表达式:后缀表达式也称之为逆波兰表达式,若将上述例子的中缀表达式转换成后缀表达式可表示为:4 1 2 + * 3 -,对于计算机来说很容易理解后缀表达式的运算优先级,即从左到右先扫描到的操作符先运算,比如上述后缀表达式先执行 “+”,再执行 “*”,后执行 “-” 法运算。具体执行情况见下面计算后缀表达式步骤。
中缀表达式转后缀表达式步骤
-
初始化两个栈:运算符栈s1和储存中间结果栈s2;
-
从左到右扫描中缀表达式;
-
遇到操作数时,将其压入s2;
-
遇到运算符时,比较其与s1栈顶运算符的优先级;
4.1. 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
4.2. 否则,若优先级比栈顶的运算符高,也将运算符压入s1;
4.3. 否则(即优先级同级或者小),将s1栈顶的运算符弹出并压入s2中,再次转到4.1与新的栈顶运算符相比较;
-
遇到括号时;
-
重复2到5步骤,直到表达式的最右边;
-
将s1中剩余的运算符依次弹出并压入s2;
-
依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
举例说明
假如中缀表达式为:1 + ((2 + 3)*4) - 5
转为后缀表达式为:1 2 3 + 4 * + 5 -
扫描到的元素 | s1(栈底->栈顶) | s2(栈底->栈顶) | 说明 |
---|---|---|---|
1 | 空 | 1 | 扫描到数字直接压入s2栈 |
+ | + | 1 | s1在这之前为空,直接压入元素+号 |
( | + ( | 1 | 扫面到的元素为左括号则直接入s1栈 |
( | + ( ( | 1 | 扫面到的元素为左括号则直接入s1栈 |
2 | + ( ( | 1 2 | 扫描到数字直接压入s2栈 |
+ | + ( ( + | 1 2 | 栈顶为左括号,直接将扫描到的元素压入s1栈 |
3 | + ( ( + | 1 2 3 | 数字 直接入s2栈 |
) | + ( | 1 2 3 + | 右括号,依次弹出s1栈顶元素并压入到s2栈中, 直到遇到左括号,并丢弃该对括号 |
* | + ( * | 1 2 3 + | 栈顶为左括号,直接压入元素 |
4 | + ( * | 1 2 3 + 4 | 数字 直接压入s2栈 |
) | + | 1 2 3 + 4 * | 右括号,依次弹出s1栈顶元素并压入到s2栈中, 直到遇到左括号,并丢弃该对括号 |
- | - | 1 2 3 + 4 * + | 与栈顶比较 优先级不高于栈顶元素 则弹出栈顶元素至s2 再与新的栈顶元素比较 栈顶为空 压入减号 |
5 | - | 1 2 3 + 4 * + 5 | 数字 直接压入s2栈 |
扫描到达右端 | 空 | 1 2 3 + 4 * + 5 - | 扫描到达右端后,将s1栈中剩余的元素依次弹出并压入到s2栈中 |
中缀表达式转后缀表达式代码实现
import java.util.*;
/**使用方法:Stack<String> result = InfixPostExpression.switch(expression)
* expression:中缀表达式,字符串类型*/
class InfixToPostExpression {
/** 静态方法:以栈的形式返回后缀表达式 */
public static Stack<String> switchList(String infixExpression) {
// 字符串分割成数组
// 遍历数组代替扫描表达式
List<String> list = InfixToPostExpression.toList(infixExpression);
System.out.println(list);
Stack<String> numStack = new Stack<>();
Stack<String> operateStack = new Stack<>();
for (int i = 0; i < list.size();) {
// 逐个取出列表中的元素
String element = list.get(i);
if (element.charAt(0) >= '0' && element.charAt(0) <= '9') {// 如果是数字直接压到numStack
numStack.push(element);
i++;
}
else if (operateStack.isEmpty()) {// 如果栈空直接将元素压入栈
operateStack.push(element);
i++;
}
else if (element.equals("+") || element.equals("-") || element.equals("*") || element.equals("/")) {// 如果为运算符 则比较优先级
if (operateStack.peek().equals("(") || operateStack.isEmpty()) {// 如果栈为空或者栈顶元素为左括号 则直接该元素压入operateStack栈中
operateStack.push(element);
i++;
}
// 如果当前运算符高于栈顶运算符 则直接将当前运算符压入operateStack栈中
else if ((element.equals("*") || element.equals("/")) && ((operateStack.peek().equals("+")) || (operateStack.peek().equals("-")))) {
operateStack.push(element);
i++;
}
else { // 否则 当前运算符优先级不大于栈顶运算符的优先级 则将栈顶运算符弹出压入到numStack栈中 再将当前运算符与下一个栈顶元素比较优先级 则i不递增
numStack.push(operateStack.pop());
}
}
else if (element.equals("(")){ // 如果当前元素为左括号 则直接压入operateStack栈中
operateStack.push(element);
i++;
}
else if (element.equals(")")) { // 如果当前元素为右括号 依次将栈顶元素弹出压到numStack栈中 直到弹出元素为左括号 然后丢弃这一对括号
while (!(operateStack.peek().equals("("))) {
numStack.push(operateStack.pop());
}
operateStack.pop();
i++;
}
}
// 当中缀表达式从左到右扫描完后 执行一下步骤
// 把operateStack中剩余的元素弹出压到numStack栈中
while (!operateStack.isEmpty()) {
numStack.push(operateStack.pop());
}
// 再将numStack的元素顺序反转 即为后缀表达式
while (!numStack.isEmpty()) {
operateStack.push(numStack.pop());
}
return operateStack;
}
/** 静态方法:将字符串分割后返回List */
public static List<String> toList(String str) {
List<String> list = new ArrayList<>();
String s = "";
for (int i = 0; i < str.length(); i++) {
// 如果是运算符 直接添加到列表中
if ((str.charAt(i) >= '9' || str.charAt(i) <= '0') && str.charAt(i) != ' ')
list.add(str.charAt(i) + "");
else if (str.charAt(i) >= '0' && str.charAt(i) <= '9') { // 如果该字符是数字 需要处理是不是多位数
s += str.charAt(i);
// 如果字符串没有到达边界 且下一个字符也是数字就进行拼接
if (i + 1 >= str.length() || str.charAt(i + 1) < '0' || str.charAt(i + 1) > '9') {
list.add(s);
s = "";
}
}
}
return list;
}
}
转换得到后缀表达式,对于写程序来计算后缀表达式相当简单,以下为具体的就算过程:
计算后缀表达式步骤
- 创建一个栈储存中间的计算结果,假如栈名为
stack
- 从左至右扫描后缀表达式
- 若为数字,将数字压入到
stack
栈中 - 若为运算符,依次弹出栈顶两个数字进行响应的运算,将运算结果压入
stack
栈中
- 若为数字,将数字压入到
- 直到扫描完后缀表达式,最后
stack
栈中只剩一个数,即为计算结果
举例说明
以上述后缀表达式为例:1 2 3 + 4 * + 5 -
扫描到的元素 | stack 栈中的动态示意 | 说明 |
---|---|---|
1 | 1 | 数字直接入栈 |
2 | 1 2 | 数字直接入栈 |
3 | 1 2 3 | 数字直接入栈 |
+ | 1 5 | 弹出3、2 ,将2 + 3结果压入栈中 |
4 | 1 5 4 | 数字直接入栈 |
* | 1 20 | 弹出5,4, 将4 * 5结果压入栈中 |
+ | 21 | 弹出20,1,将1 + 20结果压入栈中 |
5 | 21 5 | 数字直接入栈 |
- | 16 | 弹出5,21 将21 - 5结果压入栈中 |
扫描后缀表达式到达右端后,satck
栈中只剩一个元素为16,即最后栈中的元素为计算后缀表达式的结果
计算后缀表达式的代码实现
import java.util.*;
/** 计算后缀表达式 */
public class CalculateWithPostExpression {
public double culculate(String str) {
// 将中缀表达书转换成后缀表达式
Stack<String> stack = InfixToPostExpression.switchList(str);
Stack<Number> numberStack = new Stack<>();
// 根据后缀表达式的计算步骤计算
// 1.从左到右扫描后缀表达式
// 2.若为数字就压入栈中
// 3.若为操作符则依次弹出两个栈顶元素进行响应的运算,将运算结果压入栈中
// 4.扫描结束后 栈中仅剩的一个元素即为表达式的计算结果
int count = stack.size();
for (int i = 0; i < count; i++) {
// 如果为数字直接压栈
if (stack.peek().charAt(0) >= '0' && stack.peek().charAt(0) <= '9') {
numberStack.push(Double.valueOf(stack.pop()));
}
else { // 否则 进行相应的操作
double num1 = (double)numberStack.pop(); // 弹出栈顶的数字
double num2 = (double)numberStack.pop();
switch (stack.pop()) { // 进行相应的运算
case "+" : numberStack.push(num2 + num1);break;
case "-" : numberStack.push(num2 - num1);break;
case "*" : numberStack.push(num2 * num1);break;
case "/" : numberStack.push(num2 / num1);break;
default : throw new RuntimeException("输入的表达有误");
}
}
}
return (double)numberStack.pop(); // 返回栈中最后一个元素,即为计算结果
}
}
总结
想要计算机以人们的预期来理解中缀表达式的优先级,然后计算中缀表达式,此举并不简单,但是计算后缀表达式对于写程序来实现先对简单。但是难点在于中缀表达式转换成后缀表达式,当然,如果觉得转换过程麻烦,或者不理解亦或看不懂实现转换的代码,也有直接计算中缀表达式的方法,以及代码实现,等我更新。。。