逆波兰计算器
应用场景
- 输入一个逆波兰表达式(后缀表达式),使用栈(Stack), 计算其结果
- 支持小括号和多位数整数,因为这里我们主要讲的是数据结构,因此计算器进行简化,只支持 对整数的计算。
计算后缀表达式
案例: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值
关键点:判断扫描到的字符串:1.数字直接入栈;2.若是运算符,出栈两个数字并计算,计算结果放回栈中
步骤如下:
1.从左至右扫描,将 3 和 4 压入堆栈;
2.遇到+运算符,因此弹出 4 和 3(4 为栈顶元素,3 为次顶元素),计算出 3+4 的值,得 7,再将 7 入栈;
3.将 5 入栈;
4.接下来是×运算符,因此弹出 5 和 7,计算出 7×5=35,将 35 入栈;
5.将 6 入栈;
6.最后是-运算符,计算出 35-6 的值,即 29,由此得出最终结果
代码实现:
//逆波兰计算器:使用Stack计算后缀表达式
public class ReversePolandNotation {
public static void main(String[] args) {
//中缀:(30+4)*5-6
//后缀:30 4 + 5 * 6 -
String expression = "30 4 + 5 * 6 -";
//该栈用于存放数字和临时计算结果
Stack<Integer> stack = new Stack<>();
int num1;
int num2;
String opera;
String[] s = expression.split(" ");
for (String string : s) {//遍历表达式字符串
//数组元素为运算符:出栈两个数字并运算,运算结果入栈
if (isOpera(string)) {
num1 = stack.pop();
num2 = stack.pop();
opera = string;
stack.push(calcu(num1, num2, opera));
//数组元素为数字,直接入栈
} else {
stack.push(Integer.parseInt(string));
}
}
System.out.println(stack.pop());//表达式计算结果
}
//判断数组元素是否运算符
public static boolean isOpera(String s) {
return s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/");
}
//计算三个元素
public static int calcu(int num1,int num2,String opera) {
int result = 0;
switch (opera) {
case "+":
result = num2 + num1;
break;
case "-":
result = num2 - num1;
break;
case "*":
result = num2 * num1;
break;
case "/":
result = num2 / num1;
break;
}
return result;
}
}
计算中缀表达式
通过计算后缀表达式代码实现过程可以看到:
- 计算过程简单
- 可以解决较长且较复杂的表达式
- 完美解决去括号的问题
- 但正常人却很难写出来
- 因此处理中缀表达式可以先将其转换为后缀表达式,再使用逆波兰计算器完成计算
关键点提示:
-
括号问题:
当扫描到 ’ ( ’ ,直接入栈,而扫描到 ’ ) ',将s1(符号栈)栈顶运算符反复取出放入s2(储存中间结果栈),直到对应的 ’ ( ’ 出栈.此过程就是去括号
-
优先级问题:
扫描到的运算符优先级小于栈顶,取出s1栈顶入s2栈并重新将索引–指回该字符
否则即为优先级大于或等于栈顶:直接入s1栈
-
多位数问题:
仍使用字符串追加方式
-
收尾:
s1中剩余的运算符顺序放入s2,最终s2中的所有元素就是完整的后缀表达式
-
计算:
将s2中的元素出栈并存入临时集合,反向遍历元素同时,使用逆波兰计算器完成计算
具体步骤如下:
- 初始化两个栈:运算符栈 s1 和储存中间结果的栈 s2;
- 从左至右扫描中缀表达式;
- 遇到操作数时,将其压 s2;
- 遇到运算符时,比较其与 s1 栈顶运算符的优先级:
1.如果 s1 为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
2.否则,若优先级比栈顶运算符的高,也将运算符压入 s1;
3.否则,将 s1 栈顶的运算符弹出并压入到 s2 中,再次转到(4-1)与 s1 中新的栈顶运算符相比较; - 遇到括号时:
(1) 如果是左括号“(”,则直接压入 s1
(2) 如果是右括号“)”,则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃 - 重复步骤 2 至 5,直到表达式的最右边
- 将 s1 中剩余的运算符依次弹出并压入 s2
- 依次弹出 s2 中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
案例:
将中缀表达式“1+((2+3)×4)-5”转换为后缀表达式的过程如下
因此结果为 :“1 2 3 + 4 × + 5 –”
debug可观察到如下过程变化:
代码实现:
//计算中缀表达式(带括号)将一个中缀表达式转成后缀表达式并计算
public class RPNCalculator {
public static void main(String[] args) {
Stack<Character> s1 = new Stack<>();
Stack<String> s2 = new Stack<>();
String expression = "";
expression = "1+((2+3)*4)-5";//1 2 3 + 4 * + 5 -
expression = "3+(4*(1+1)+1)*2";//出栈+*2+1*+1143 后缀:3 4 1 1 + * 1 + 2 * +
int index = 0;
String num = "";
ArrayList<Character> characters;
while (true) {
if (index>=expression.length()) {//扫描完毕退出循环
break;
}
char ch = expression.charAt(index);
index ++;
if (isOpera(ch)) {//字符为运算符
//以下条件满足任一直接入s1栈
//1.栈为空
//2.栈顶为'('
//3.该字符为'('
if (s1.empty() || peek(s1) == '('||ch=='(') {
s1.push(ch);
//判断该字符为')'执行:
//循环取出s1栈顶入s2直到取出'('为止
} else if (ch == ')') {
while (true) {
if (peek(s1) == '(') {
s1.pop();
break;
}
s2.push("" + s1.pop());
}
}else {//剩余情况:
//该字符优先级小于栈顶:取出s1栈顶入s2栈并重新将索引--指回该字符
if (priority(ch) < priority(s1.peek())) {
s2.push("" + s1.pop());
index--;
//否则即为优先级大于或等于栈顶:直接入s1栈
} else {
s1.push(ch);
}
}
} else {//字符为数字
//将该字符追加到字符串末尾
num += ch;
//判断是否为最后一个字符,或下一个字符为运算符:直接入s2栈,然后数字字符串置空
if (index==expression.length()||isOpera(expression.charAt(index))) {
s2.push(num);
num = "";
}
}
}
//将s1剩余运算符出栈同时入s2栈
while (true) {
if (s1.empty()) {
break;
}
s2.push("" + s1.pop());
}
//到此行,中缀转后缀的完整的后缀表达式入s2栈完毕
/*while (true) {
if (s2.empty()) {
break;
}
System.out.print(s2.pop());
}*/
//s2元素出栈
ArrayList<String> elements = new ArrayList<>();
while (true) {
if (s2.empty()) {
break;
}
elements.add(s2.pop());
}
//数字栈,用于存放临时计算结果
Stack<Integer> stack = new Stack<>();
int num1;
int num2;
String opera;
//倒序遍历集合中的元素,使用逆波兰计算器,借助一个数字栈计算,将计算结果存入该栈
for (int i = elements.size()-1; i >=0 ; i--) {
//数组元素为运算符:出栈两个,运算结果入栈
if (isOpera(elements.get(i))) {
num1 = stack.pop();
num2 = stack.pop();
opera = elements.get(i);
stack.push(calcu(num1, num2, opera));
//数组元素为数字,直接入栈
} else {
stack.push(Integer.parseInt(elements.get(i)));
}
}
System.out.println(stack.pop());//表达式计算结果
}
//判断字符是否运算符
public static boolean isOpera(char ch) {
return ch == '+' || ch == '-' || ch == '*' || ch == '/'||ch=='('||ch==')';
}
//判断数组元素(字符串)是否运算符
public static boolean isOpera(String s) {
return s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/");
}
//计算三个元素
public static int calcu(int num1,int num2,String opera) {
int result = 0;
switch (opera) {
case "+":
result = num2 + num1;
break;
case "-":
result = num2 - num1;
break;
case "*":
result = num2 * num1;
break;
case "/":
result = num2 / num1;
break;
}
return result;
}
//返回操作符的优先级
public static int priority(char opera) {
if (opera == '*' || opera == '/') {
return 1;
}else if (opera == '+' || opera == '-') {
return 0;
}
return -1;
}
//返回栈顶元素
public static Character peek(Stack<Character> stack) {
Character result =stack.pop();
stack.push(result);
return result;
}
}
代码优化
思路:
- 将中缀表达式存入集合
- 中缀表达式集合解析并转换成后缀表达式存入集合
- 逆波兰计算器计算后缀表达式
步骤:
-
编写转换方法,方法中将中缀表达式字符串中元素 区分多位数和运算符两种元素按顺序添加到集合
-
编写解析方法将中缀转后缀:
这里只使用一个栈和一个集合搭配,因为如果使用两个栈,存放中间结果的栈最终出栈元素还需 反转 才得到 想要的后缀表达式,使用集合存放简化了这一步骤
1)遍历传入的集合,判断是否多位数,直接放入集合
2)如果是左括号直接入栈
3)如果是右括号,反复观察栈顶是否是左括号,反复 出栈并放入集合直到遇左括号
4)剩余情况就是运算符了:按优先级操作
5)收尾:将剩余栈中元素存放在集合
-
逆波兰计算器计算后缀表达式
//优化代码并封装方法实现中缀转后缀再计算
public class HSPCalculator {
public static void main(String[] args) {
String infixExpression = "1+((2+3)*4)-5";//中缀表达式
List<String> infixExpressionList = toInfixExpressionList(infixExpression);//中缀拆开放入集合
List<String> suffixExpressionList = parseSuffixExpressionList(infixExpressionList);//后缀表达式1 2 3 + 4 * + 5 -
System.out.println(calc(suffixExpressionList));
}
//逆波兰计算器
public static int calc(List<String> ls) {
Stack<Integer> stack = new Stack<>();
int num1;
int num2;
String opera;
for (String string : ls) {//遍历表达式字符串
//数组元素为运算符:出栈两个数字并运算,运算结果入栈
if (isOpera(string)) {
num1 = stack.pop();
num2 = stack.pop();
opera = string;
stack.push(calcu(num1, num2, opera));
//数组元素为数字,直接入栈
} else {
stack.push(Integer.parseInt(string));
}
}
return stack.pop();//表达式计算结果
}
//将中缀List转成后缀List
public static List<String> parseSuffixExpressionList(List<String> ls) {
Stack<String> s1 = new Stack<>();//符号栈
ArrayList<String> s2 = new ArrayList<>();//存放中间结果的集合
for (String item : ls) {
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 {
while (s1.size() != 0 && priority(s1.peek()) >= priority(item)) {
s2.add(s1.pop());
}
s1.push(item);
}
}
while (s1.size() != 0) {
s2.add(s1.pop());
}
return s2;
}
//方法:将中缀表达式转成List:多位数和运算符分别以字符串类型存入集合
public static List<String> toInfixExpressionList(String s) {
ArrayList<String> ls = new ArrayList<>();
int index = 0;
String num;
char ch;
do {
if ((ch = s.charAt(index)) < 48 || (ch = s.charAt(index)) > 57) {
ls.add("" + ch);
index++;
} else {
num = "";
while (index < s.length() && ((ch = s.charAt(index)) >= 48 && (ch = s.charAt(index)) <= 57)) {
num += ch;
index++;
}
ls.add(num);
}
} while (index < s.length());
return ls;
}
//返回操作符的优先级
public static int priority(String opera) {
if (opera .equals("*") || opera .equals("/") ) {
return 2;
}else if (opera .equals("+") || opera .equals("-")) {
return 1;
}
return 0;
}
//判断数组元素是否运算符
public static boolean isOpera(String s) {
return s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/");
}
//计算三个元素
public static int calcu(int num1,int num2,String opera) {
int result = 0;
switch (opera) {
case "+":
result = num2 + num1;
break;
case "-":
result = num2 - num1;
break;
case "*":
result = num2 * num1;
break;
case "/":
result = num2 / num1;
break;
}
return result;
}
}