前缀、中缀、后缀表达式
前缀表达式
前缀表达式:也称为波兰表达式,前缀表达式的运算符位于操作数之前,例如(3+4)×5-6对应的前缀表达式就是 - × + 3 4 5 6。 (3+4)×5-6是中缀表达式
前缀表达式的计算机求值过程
从右至左扫描表达式,遇到数字时,将数字压入到数栈,遇到运算符时,弹出栈顶的两个数(栈顶元素和次栈顶元素),并使用运算符进行相应的计算,然后将结果入栈,重复上述过程,直到表达式的最左端,最后运算出的值即为表达式的结果。
举个栗子(3+4)×5-6对应的前缀表达式就是 - × + 3 4 5 6 (这个前缀表达式需要根据中缀表达式的先后顺序得到)
1、从右至左扫描表达式,将 6 5 4 3 压入数栈。
2、遇到运算符 + 取出栈顶和次栈顶元素 3 和 4 执行 3+4 并将结果7入栈。
3、遇到运算符 × 取出栈顶和次栈顶元素 7 和5 执行 7×5 并将结果35入栈。
4、遇到运算符 - 取出栈顶和次栈顶元素 35 和 6 执行获得最终的结果。
中缀表达式
1、中缀表达式就是常见的运算表达式,例如:(3+4)×5-6
2、中缀表达式比较符合人类的思维,也是我们从小训练的思路,但是对于计算机却不好操作,因此,再计算中会将中缀表达式转换成其他表达式来进行操作(一般是转成后缀表达式)。
后缀表达式
后缀表达式也叫做逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后,例如 (3+4)×5-6的后缀表达式是: 3 4 + 5 × 6 -
后缀表达式计算机求值过程
1、从左至右进行扫描,将 3 和 4 压入栈, 4为栈顶元素 3 为次栈顶元素,扫描到 + 运算符,弹出栈顶和次栈顶元素并执行 3 + 4运算,得到结果7,并将 7 压入栈中。
2、将5入栈,扫描到 × 取出 栈顶和次栈顶元素 执行 7×5 并将结果35入栈
3、将6入栈 ,扫描到 - 取出 栈顶和次栈顶元素 执行 35-6 得到结果
后缀表达是总是 次栈顶元素再前 栈顶元素在后进行运算。跟前缀表达式有区别。
逆波兰计算器(后缀表达式计算)
需求:先来个简单的,直接输入逆波兰表达式,使用栈计算结果。
思路分析 就是后缀表达式的求值过程。代码实现
package com.example.data.sparse.stack;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Stack;
import java.util.stream.Collectors;
/**
* @author zjt
* 逆波兰表达式
*/
public class ReversePolishNotation {
public static void main(String[] args) {
// 定义一个后缀表达式 为了方便使用,表达式中间使用空格隔开
// 中缀表达式 : (30+4)×5-6
String postfixExpression = "30 4 + 5 * 6 - ";
// 思路 先将表达式放入ArrayList中,配合栈完成计算
List<String> list = Arrays.stream(postfixExpression.split(" "))
.filter(Objects::nonNull).collect(Collectors.toList());
System.out.println(list);
System.out.println("计算的结果"+cal(list));
}
public static int cal(List<String> list) {
// 创建一个栈
Stack<String> stack = new Stack<>();
list.forEach(item -> {
// 使用正则表达式取出数字
if (item.matches("\\d+")) { // 匹配多位数
// 入栈
stack.push(item);
} else {
// 认为是运算符号 pop出两个数并运算
int n1 = Integer.parseInt(stack.pop()); // 栈顶
int n2 = Integer.parseInt(stack.pop()); // 次栈顶
int result;
// 后缀表达式的运算 总是次栈顶 在前 栈顶在后
switch (item) {
case "+":
result = n2 + n1;
break;
case "-":
result = n2 - n1;
break;
case "*":
result = n2 * n1;
break;
case "/":
result = n2 / n1;
break;
default:
throw new RuntimeException("运算符有误");
}
stack.push(String.valueOf(result));
}
});
// 最后留在栈中的数据就是运算结果
return Integer.parseInt(stack.pop());
}
}
中缀表达式转后缀表达式
上面的代码是直接计算的后缀表达式,对于我们来说,后缀表达式看起来并不容易。所以还是需要中缀表达式转成后缀表达式去计算。
中缀表达式转后缀表达式具体步骤如下:
1、初始化两个栈,运算符栈s1和存储中间结果的栈s2
2、从左到右扫描中缀表达式
3、遇到操作数,将其压入s2
4、遇到运算符时,比较其于s1栈顶运算符的优先级:
4.1、若s1为空或栈顶运算符为左括号"(",直接将此运算符压入栈s1中。
4.2、若优先级比栈顶运算符高,也将运算符压入s1栈中。
4.3、否则,将s1栈顶的运算符弹出并压入的s2中,再次转到4.1与s1中新的栈顶运算符比较。
5、遇到括号时;
5.1、如果时左括号"(",直接压入s1
5.2、如果是右括号,")",则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止。
6、重复步骤2-5直到表达式的最右边。
7、将s1中剩余的运算符压入s2
8、依次弹出s2中元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。
举例:将中缀表达式 1 + ( ( 2 + 3 ) × 4 ) - 5 转成后缀表达式
扫描到的元素 | s2 栈底到栈顶 | s1栈底到栈顶 | 说明 |
---|---|---|---|
1 | 1 | 空 | 数字,直接入栈 |
+ | 1 | + | s1为空,运算符直接入栈 |
( | 1 | + ( | 左括号直接入栈 |
( | 1 | + ( ( | 左括号直接入栈 |
2 | 1 2 | + ( ( | 数字,直接入栈 |
+ | 1 2 | + ( ( + | s1栈顶为左括号,运算符直接入栈 |
3 | 1 2 3 | + ( ( + | 数字,直接入栈 |
) | 1 2 3 + | + ( | 扫描到右括号弹出运算符直至遇到左括号 |
× | 1 2 3 + | + ( × | s1栈顶为左括号,运算符直接入栈 |
4 | 1 2 3 + 4 | + ( × | 数字,直接入栈 |
) | 1 2 3 + 4 × | + | 扫描到右括号弹出运算符直至遇到左括号 |
- | 1 2 3 + 4 × + | - | -与+优先级统计因此需要弹出 + 压入 - |
5 | 1 2 3 + 4 × + 5 | - | 数字,直接入栈 |
到达最右端 | 1 2 3 + 4 × + 5 - | 空 | s1中剩余的运算符压入s2 |
代码实现
class NotationClient {
public static void main(String[] args) {
// 将中缀表达式转成后缀表达式
// 对 1 + ( ( 2 + 3 ) * 4 ) - 5 -> 1 2 3 + 4 * + 5 -
// 对字符串操作不方便,先将字符串转成ArrayList 然后进行操作
String expression = "101+((2+30)*4)-5";
// 将中缀表达式转为 list
List<String> infixExpressionList = toInfixExpression(expression);
// 字符串转中缀表达式
System.out.println(infixExpressionList);
// 后缀表达式
List<String> list = toPostfixExpressionList(infixExpressionList);
System.out.println(list);
// 调用之前的后缀表达式计算
System.out.println("计算的结果:" + expression + "=" + ReversePolishNotation.cal(list));
}
// 中缀表达式转List
public static List<String> toInfixExpression(String str) {
int i = 0; // 想当于指针,用于变量中缀表达式字符串
StringBuilder sb; //用于多位数的拼接
char c; // 临时变量,没遍历到一个字符存入 c中
List<String> list = new ArrayList<>();
do {
// 如果 是一个非数字加入到list中
if (!String.valueOf(c = str.charAt(i)).matches("\\d+")) { // 利用正则表达式判断该字符是否是数字
list.add(String.valueOf(c));
i++;
} else {
// 如果是数字需要考虑拼接问题
sb = new StringBuilder(); // 先将sb置空
while (i < str.length() && String.valueOf(c = str.charAt(i)).matches("\\d+")) {
sb.append(c);
i++;
}
list.add(sb.toString());
}
} while (i < str.length());
return list;
}
// 中缀表达式List转后缀表达式 1 + ( ( 2 + 3 ) * 4 ) - 5 -> 1 2 3 + 4 * + 5 -
public static List<String> toPostfixExpressionList(List<String> infixExpressionList) {
// 初始化符号栈
Stack<String> s1 = new Stack<>();
// 因为栈s2在整个转换过程中没有弹出操作,而且需要逆序输出,这里使用ArrayList 代替。
List<String> s2 = new ArrayList<>(); // 用于存储中间结果的List
infixExpressionList.forEach(item -> {
// 如果式一个数 就直接加入list
if (item.matches("\\d+")) {
s2.add(item);
} else if (item.equals("(")) {
s1.push(item);
} else if (item.equals(")")) { // 如果是右括号 需要一直弹出,直到找到第一个左括号
// 直到有偷看到第一个左括号
while (!s1.peek().equals("(")) {
s2.add(s1.pop());
}
s1.pop();// 消除小括号 这个很重要
} else {
// 当item的优先级小于等于s1栈顶运算符优先级 将s1栈顶的运算符弹出并加入的s2中,再次转到4.1与s1中新的栈顶运算符比较。
// 优先级不能有括号
while (!s1.isEmpty() && !s1.peek().equals("(") && Operation.getValue(s1.peek()) >= Operation.getValue(item)) {
s2.add(s1.pop());
}
// 将item 入栈
s1.push(item);
}
});
while (!s1.isEmpty()) {
s2.add(s1.pop()); // 按照顺序输出就是对于的后缀表达式
}
return s2;
}
}
// 返回优先级
@Data
class Operation {
private static int ADD = 1; // 加
private static int SUB = 1; // 减
private static int MUL = 2; // 乘
private static int DIV = 2; // 除
public static int getValue(String opera) {
switch (opera) {
case "+":
return ADD;
case "-":
return SUB;
case "*":
return MUL;
case "/":
return DIV;
default:
throw new RuntimeException("运算符有误");
}
}
}