平时咱们进行数学计算应用的常见书写形式就是中断表达式,即每一个运算符号都位于计算数的两头,如下:
$$
(1+2)\div3
$$
而这对于计算机进行求取后果来说,并不是一个最优的计划,毕竟每次读取到一个运算符,都得判断一次优先级,并且须要确定两个计算数的指标地位。在四则运算的根底之上,还在增加一个括号的应用,这将更为简单,中断表达式将更加对计算机不敌对。那什么样的表达式构造会是计算机较为优化的呢?
答案就是逆波兰表达式,也叫做后缀表达式,它最大的益处就是无需再思考运算符的优先级,也无需思考计算数指标地位。当然,实现逆波兰表达式的转换,仍须要对运算发优先级进行判断,但给出一个逆波兰表达式,就能够间接读取计算,无需思考优先级。
逆波兰表达式特色:
- 不存在括号运算符来束缚,也无需思考运算符的优先级
- 运算符的两个计算数位于运算符前两位
- 严格遵循“从左到右”运算, 越后的运算符越晚计算
逆波兰表达式如下:
$$
1\quad2\quad3\quad\div\quad-
$$
而实现一个带有括号的应用的中断表达式转换为逆波兰表达式的思路,既能够以二叉树的后序遍从来实现,也能够应用栈的形式的实现,这里将采纳栈的形式实现。对于括号的解决,咱们能够从它的性质下手,括号的呈现肯定是成对存在的,有左括号,必然有右括号。只有在读取的过程中,遇到左括号就入栈,无论多少重左括号,直至遇到右括号,出栈直至左括号出栈,期间进行数字运算。
算法思路
实现一个中断表达式转换成后缀表达式,再根据后缀表达式计算出最初的运算后果的基本思路如下:
-
将中断表达式转换为后缀表达式
- 定义两个栈:符号栈 s1、两头后果栈 s2
注:s2 采纳 add() 办法入栈,保障程序输入为所需后果,且其无需弹栈,采纳 List 作为栈的存储构造,s1 都能够。
- 定义一个中断表达式字符串 expression2
- 遍历中断表达式,将中断表达式转成对应的 list
- 一一遍历中断表达式,并判断字符类型
-
数字:
入栈两头后果栈 s2
-
左括号:
入栈符号栈 s1
-
右括号:
顺次弹出 s1 栈顶元素,并压入 s2,直至遇到左括号为止,将左括号弹出 s1 栈,打消括号
-
加减乘除运算符:
获取以后遍历字符优先级,当小于或等于栈顶优先级,或为左括号时,s1 栈顶运算符弹出并压入 s2 中,最初将以后字符压入 s1 栈中
- 将两头后果栈程序输入为最终后果
如下即为应用栈构造实现逆波兰表达式的基本思路图示
-
读取并计算后缀表达式后果
- 定义后果栈
- 对逆波兰表达式一一遍历,判断字符
- 若为数字,压入栈中
- 若为运算符,顺次弹出栈中两个元素,进行运算,将后果入栈
- 遍历完结,将后果出栈,即为最终答案
代码如下
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
public class RPN {public static void main(String[] args) {
// 实现一个中断表达式转换成后缀表达式
// 阐明
// 1. 1+((2+3)*4)-5 => 1 2 3 + 4 * + 5 -
// 2. 因为间接对 str 进行操作,不不便,因而先将“1+((2+3)*4)-5”=> 转成中断表达式
// 即“1+((2+3)*4)-5”=> ArrayList[1,+,(,(,2,+,3,),*,4,),-,5]
// 3. 将失去的中断表达式对应的 list 转成后缀表达式对应的 list
// 即 ArrayList[1,+,(,(,2,+,3,),*,4,),-,5] => ArrayList[1, 2 ,3, +, 4, *, +, 5, -]
Scanner in = new Scanner(System.in);
String expression2 = in.next();
List<String> infixExpressionList = toInfixExpressionList(expression2);
System.out.println("中断表达式对应 List" + infixExpressionList);
List<String> parseSuffixExpressionList = parseSuffixExpressionList(infixExpressionList);
System.out.println("后缀表达式对应的 list" + parseSuffixExpressionList);
System.out.println(expression2 + "后果是" + calculate(parseSuffixExpressionList));
}
// 办法:将失去的中断表达式对应的 list 转换成 后缀表达式对应的 list
public static List<String> parseSuffixExpressionList(List<String> ls) {
// 定义两个栈
Stack<String> s1 = new Stack<String>(); // 符号栈
List<String> s2 = new ArrayList<String>(); // 存储两头后果的栈,因为该栈始终没有弹出过,所以应用 List 存储
// 遍历 ls
for (String item : ls) {
// 如果是一个数就入栈,入栈 s2,\\d+ 匹配数字且至多呈现一次
if (item.matches("\\d+")) {s2.add(item);
} else if (item.equals("(")) {
// 如果是左括号就入栈,入栈 s1
s1.push(item);
} else if (item.equals(")")) {
// 如果是右括号,则顺次弹出 S1 栈顶的运算符,并压入 S2,直到遇到左括号为止
while (!s1.peek().equals("(")) {s2.add(s1.pop());
}
s1.pop(); // 将 ( 弹出 s1 栈,打消括号,小括号} else {
// 当 item 的优先级,小于或者等于栈顶运算符的优先级的时候,就应该将 s1 栈顶的运算夫弹出并压入 s2 中
while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)) {s2.add(s1.pop());
}
// 还须要将 item 压入栈
s1.push(item);
}
}
// 将 s1 中残余的元素压入 s2
while (s1.size() != 0) {s2.add(s1.pop());
}
return s2; // 因为是寄存到 List,因而按顺组输入就是对应的逆波兰表达式的 List
}
// 办法:将中断表达式转成对应的 list
public static List<String> toInfixExpressionList(String s) {
// 定义一个 List,寄存中断表达式 对应的内容
List<String> ls = new ArrayList<>();
int i = 0; // 这个相当于一个指针,用于遍历中断表达式字符串 s
String str = ""; // 用于对多位数的拼接工作
char c; // 每遍历到一个字符就放到 c
do {
// 如果 c 事一个非数字,咱们就须要退出到 ls 中去
if ((c = s.charAt(i)) < '0' || (c = s.charAt(i)) > '9') {ls.add("" + c);
i++; // 须要后移
} else {
// 如果是数字,须要思考多位数的问题
str = ""; // 先将 str 置成空串
while (i < s.length() && ((c = s.charAt(i)) >= '0' && (c = s.charAt(i)) <= '9')) {
str += c; // 拼接
i++;
}
ls.add(str);
}
}
while (i < s.length());
return ls;
}
// 将一个逆波兰表达式,顺次将数据和运算符 放入 ArrayList
public static List<String> getListString(String expression) {
// 将 expression 宰割
String[] split = expression.split(" ");
List<String> list = new ArrayList<String>();
for (String ele : split) {list.add(ele);
}
return list;
}
// 实现对逆波兰表达式的运算
/*
1. 从左至右扫描,将 3 和 4 压入堆栈
2. 遇到 + 运算符,因而弹出 4 和 3(4 为栈顶元素,3 为次顶元素),计算出 3 + 4 的值,得 7,再将 7 入栈
3. 将 5 入栈
4. 接下来是 X 运算符,因而弹出 5 和 7,计算出 7 X 5 = 35,将 35 入栈
5. 将 6 入栈
6. 最初是 - 运算符,计算出 35 - 6(减法运算或者除法运算的时候,后缀表达式是 次顶元素 减去或除以 堆顶元素)的值,即 29,由此得出最终后果
*/
public static int calculate(List<String> ls) {
// 创立一个栈, 只须要一个栈即可
Stack<String> stack = new Stack<>();
// 遍历 ls
for (String item : ls) {
// 这里应用一个正则表达式取出数
if (item.matches("\\d+")) {
// 匹配的是多位数
stack.push(item);
} else {
// pop 出两个数并运算,在入栈
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = 0;
if (item.equals("+")) {res = num1 + num2;} else if (item.equals("-")) {res = num1 - num2;} else if (item.equals("*")) {res = num1 * num2;} else if (item.equals("/")) {res = num1 / num2;} else {throw new RuntimeException("符号有问题");
}
// 把 res 入栈, 入栈的时候要将 res 转换成字符,因为咱们的栈是字符串类型的
stack.push(res + "");
}
}
// 最初留在 stack 的数据是运算后果
return Integer.parseInt(stack.pop());
}
}
// 编写一个类 Operation 能够返回一个运算符 对应的优先级
class Operation {
private static int ADD = 1;
private static int SUB = 1;
private static int MUL = 2;
private static int DIV = 3;
// 写一个办法,返回对应的
public static int getValue(String operation) {
int result = 0;
switch (operation) {
case "+":
result = ADD;
break;
case "-":
result = SUB;
break;
case "*":
result = MUL;
break;
case "/":
result = DIV;
break;
}
return result;
}
}