问题概述
我们完成一个逆波兰计算器,要求完成如下任务
- 输入一个中缀表达式,转成后缀表达式(逆波兰表达式),使用stack计算结果
- 要求支持小括号,和多位整数,我们暂时不考虑小数问题
逆波兰表达式书写
逆波兰表达式(Reverse Polish notation, RPN,或逆波兰记法),也叫后缀表达式,运算符写在数值的后面
为什么要使用逆波兰表达式呢
实现逆波兰表达式其实并不是很难,但是为什么要将看似简单的中缀表达式转成后缀表达式呢?
其实中缀表达式只是对人类而言是简单的,然而对于计算机来说,中缀表达式是一个非常复杂的结构。而后缀表达式对于计算机是一个很容易理解的结构。因为计算即执行的是一个栈结构,执行先进后出的顺序
转后缀表达式步骤
- 先准备两个栈S1,S2,分别用于存储符号和存储数值,从中缀表达式的最左边开始取值。
- 当取出的是一个操作数,则直接送入数值栈S2。
- 当取出是一个操作符时,如果栈顶的为空则直接送入操作符栈S1,如果当前操作符级别(不包括括号运算符)大于栈顶操作符时,也直接压入符号栈S1;当前操作符小于栈顶操作符时,弹出符号栈中的符号送入数值栈S2直至当前操作符级别大于栈顶操作符,将当前操作符压入操作符栈,当栈顶为(括号时当前操作符直接压入符号栈S1中。
- 当取出操作符为(括号时,直接压入操作符栈中
- 当取出操作符为)括号时,弹出操作符直至栈顶符号为(,最后将(也弹出。
- 重复之前的动作,直至遍历完算式
- 最后逆序打印我们的数值栈S2即可得到我们的逆波兰表达式。
举个例子
将1 + ((2 + 3)* 4)- 5 转成后缀表达式为1 2 3 + 4 * 5 - +
取出的操作数 | S1栈(栈底 --> 栈顶) | S2栈(栈底 --> 栈顶) | 步骤说明 |
---|---|---|---|
1 | 空 | 1 | 将数值1压入数值栈中 |
+ | + | 1 | 符号栈中为空,直接压入符号栈 |
( | + ( | 1 | 操作符为(直接压入符号栈中 |
( | + ( ( | 1 | 操作符为( 直接压入符号栈中 |
2 | + ( ( | 1 2 | 取出操作数为数值直接压入数值栈中 |
+ | + ( ( + | 1 2 | 符号栈的栈顶为(括号直接压入符号栈中 |
3 | + ( ( + | 1 2 3 | 取出操作数为数值直接压入数值栈中 |
) | + ( | 1 2 3 + | 取出操作符为)弹出符号栈中的符号直到弹出最近的(,并将弹出的符号压入数值栈中 |
* | + ( * | 1 2 3 + | 取出操作符为*并且当前操作符级别大于栈顶操作符级别,直接压入符号栈 |
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 |
使用逆波兰表达式计算结果
- 创建一个栈S1用于存储数值,从左到右遍历逆波兰表达式(后缀表达式)
- 取出数为数值压入栈中
- 取出数为符号时,弹出栈中两个数值进行计算,并将计算结果压入数值栈中。
- 重复2,3部直至到后缀表达式底部
取出的数值 | 栈(栈底 – > 栈顶) | 步骤说明 |
---|---|---|
1 | 1 | 操作数为数值压入栈中 |
2 | 1 2 | 操作数为数值压入栈中 |
3 | 1 2 3 | 操作数为数值压入栈中 |
+ | 1 5 | 操作数为符号,弹出两个数值进行运算,将运算结果压入栈中 |
4 | 1 5 4 | 操作数为数值压入栈中 |
* | 1 20 | 操作数为符号,弹出两个数值进行运算,将运算结果压入栈中 |
5 | 1 20 5 | 操作数为数值压入栈中 |
- | 1 15 | 操作数为符号,弹出两个数值进行运算,将运算结果压入栈中 |
+ | 16 | 操作数为符号,弹出两个数值进行运算,将运算结果压入栈中 |
代码实现简单计算器功能
import java.util.List;
import java.util.ArrayList;
public class Calculator {
public static void main(String[] args) {
String express = "1+((2+3)*4)-5":
List<String> suffixExpression = SuffixExpression.toSuffixExpression(list);
int calculate = SuffixExpression.calculate(suffixExpression);
System.out.println(calculate);
}
/**
* 将一个算式解析成一个个元素
* @param 需要截取的字符串
*/
public static List<String> strToList(String str) {
if (str == null || str.length() <= 0) {
throw new IllegalArgumentException();
}
List<String> list = new ArrayList<String>();
for (int i = 0;i < str.length();i++) {
if (str.charAt() < 48 || str.charAt(i) > 57) {
list.add("" + str.charAt(i));
} else {
String sub = "":
while (i < str.length() && str.charAt(i) >= 48 && str.charAt(i) <= 57) {
sub += str.charAt(i);
list.add(sub);
i++;
}
}
}
return list;
}
/**
* 将中缀表达式转逆波兰表达式(后缀表达式)
* @param 需要转的中缀表达式
*/
public static List<String> toSuffixExpression(List<String> list) {
if (list == null || list.isEmpty()) {
throw new IllegalArgumentException();
}
// 用于存放操作符的
Stack<String> stack = new Stack<String>();
// 用于存放数值,由于最后我们是需要逆序输出数值栈,所以这里直接用集合替代栈,可以免去逆序打印
List<String> suffix = new ArrayList<String>();
for (String str:list) {
if (str.matches("\\d")) {
suffix.add(str);
} else if (str.equals("(")) {
stack.push(str);
} else if (str.equals(")")) {
while (!stack.peek().equals("(")) {
suffix.add(stack.pop());
}
stack.pop();
} else {
while (stack.size() > 0 && getValue(str) < getValue(stack.peek())) {
suffix.add(stack.pop());
}
stack.push(str);
}
}
while (stack.size() > 0){
suffix.add(stack.pop());
}
}
/**
* 根据逆波兰表达式计算结果
* @param 需要计算的逆波兰表达式
*/
public static int calculator(List<String> list) {
if (list == null || list.isEmpty()) {
throw new IllegalArgumentException();
}
Stack<String> stack = new Stack<String>();
for (String str:list) {
if (str.matches("\\d")) {
stack.push(str);
} else {
int num1 = Integer.parseInt(stack.pop());
int num2 = Integer.parseInt(stack.pop());
if (str.equals("+")){
stack.push(String.valueOf(num1+num2));
}else if (str.equals("-")){
stack.push(String.valueOf(num2-num1));
}else if (str.equals("*")){
stack.push(String.valueOf(num1*num2));
}else if (str.equals("/")){
stack.push(String.valueOf(num2/num1));
}
}
}
return Integer.parseInt(stack.pop());
}
/**
* 计算输入字符的优先级
* @param 需要判断的字符
*/
public static int getValue(String str){
int result;
switch (str){
case "+":
result = 1;
break;
case "-":
result = 1;
break;
case "*":
result = 2;
break;
case "/":
result = 2;
break;
default:
result = 0;
break;
}
return result;
}
}
代码写完,小弟只是能力有限,难免有漏洞,欢迎各路大神指点评论!!!