逆波兰表达式又叫做后缀表达式,逆波兰表达式把运算量写在前面,把运算符写在后面。
我们平时书写的表达式都是中缀表达式,这个表达式更加方便我们进行计算。但是相对于计算机而言,逆波兰表达式运算更加方便。只需要入栈和出栈两种操作就可以搞定所有运算,遇到操作数就入栈,遇到运算符就出栈,然后将栈顶的两个操作数进行相应运算。
所以,要想用代码实现计算器的首要步骤,就是将 中缀表达式 转为 后缀表达式。
中缀表达式转后缀表达式
步骤如下:
-
初始化两个栈:运算符栈 s1 和储存中间结果的栈 s2;
-
从左至右扫描中缀表达式;
-
遇到操作数时,将其压 s2;
-
遇到运算符时,比较其与 s1 栈顶运算符的优先级:
①. 如果 s1 为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
②. 否则,若优先级比栈顶运算符的高,也将运算符压入 s1;
③. 否则,将 s1 栈顶的运算符弹出并压入到 s2 中,再次转到4.①与 s1 中新的栈顶运算符相比较;
-
遇到括号时:
①. 如果是左括号"(",则直接压入 s1;
②. 如果是右括号")",则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃;
-
重复步骤 2—5,直到扫描到表达式的最右边。
-
将 s1 中剩余的运算符依次弹出并压入 s2。
-
依次弹出 s2 中的元素并输出,结果的逆序即为 中缀表达式 对应的 后缀表达式。
形如:
中缀表达式“1+((2+3)×4)-5”,转为后缀表达式的结果是:
1 2 3 + 4 × + 5 –
代码实现
下面的代码只能进行整数的运算。
首先声明一个常量类,里面记录着运算符的字符。
public class Constants {
public static final String ADD_SYMBOL = "+";
public static final String SUB_SYMBOL = "-";
public static final String MUL_SYMBOL = "*";
public static final String DIV_SYMBOL = "/";
}
接着创建一个返回运算符优先级的类。
public class Operation {
/**
* 加
*/
private static int ADD = 1;
/**
* 减
*/
private static int SUB = 1;
/**
* 乘
*/
private static int MUL = 2;
/**
* 除
*/
private static int DIV = 2;
/**
* TODO 返回对应的优先级
*/
public static int getValue(String operand) {
int result = 0;
switch (operand) {
case Constants.ADD_SYMBOL:
result = ADD;
break;
case Constants.SUB_SYMBOL:
result = SUB;
break;
case Constants.MUL_SYMBOL:
result = MUL;
break;
case Constants.DIV_SYMBOL:
result = DIV;
break;
default:
System.out.println("不存在该运算符");
break;
}
return result;
}
}
最后就是核心功能了。编写中缀表达式转后缀表达式、计算。
public class ReversePolandCalculator {
/**
* TODO 将中缀表达式转 list 集合
*
* @param str
* @return java.util.List<java.lang.String>
*/
public static List<String> infixExpressionToList(String str) {
List<String> list = new ArrayList<>();
// 作为一个指针
int index = 0;
int zeroAscii = 48;
int nineAscii = 57;
// 如果这个操作数是多位数
StringBuilder mStr;
// 存放每一个字符
char ch;
do {
// 如果 ch 是一个 非数字,需要加入到 list(0-9的ASCII值是48-57)
if ((ch = str.charAt(index)) < zeroAscii || (ch = str.charAt(index)) > nineAscii) {
list.add("" + ch);
// 指针后移
index++;
} else {
// 如果是一个数,需要考虑多位数
mStr = new StringBuilder();
while (index < str.length() && (ch = str.charAt(index)) >= zeroAscii && (ch = str.charAt(index)) <= nineAscii) {
mStr.append(ch);
index++;
}
list.add(mStr.toString());
}
} while (index < str.length());
return list;
}
/**
* TODO 将 中缀表达式的 List 集合转 后缀表达式
*
* @param infixList
* @return java.util.List<java.lang.String>
*/
public static List<String> infixToSuffixExpressionList(List<String> infixList) {
// 符号栈
Stack<String> stack = new Stack<>();
// 存储中间结果的
List<String> resultList = new ArrayList<>();
String leftBracket = "(";
String rightBracket = ")";
// 遍历
for (String item : infixList) {
// 如果是一个数,加入 resultList
if (item.matches("\\d+")) {
resultList.add(item);
} else if (item.equals(leftBracket)) {
stack.push(item);
} else if (item.equals(rightBracket)) {
// 如果是 右括号,则依次弹出 stack 栈顶的运算符,并压入 resultList,直到遇到左括号为止,此时将这一对括号丢弃
while (!stack.peek().equals(leftBracket)) {
resultList.add(stack.pop());
}
// 将 ( 弹出 stack 栈,消除小括号
stack.pop();
} else {
// 当 item 的优先级小于等于 stack栈顶运算符,将 stack 栈顶的运算符弹出并加入到 resultList 中,再次与 stack 中新的栈顶运算符相比较
while (stack.size() != 0 && Operation.getValue(stack.peek()) >= Operation.getValue(item)) {
resultList.add(stack.pop());
}
// 将 此时 的 item压入栈
stack.push(item);
}
}
// 将 stack 中剩余的运算符依次弹出并加入到 resultList
while (stack.size() != 0) {
resultList.add(stack.pop());
}
// 因为 存放到 List中,因此按顺序输出就是 对应的 后缀表达式
return resultList;
}
/**
* TODO 计算
*
* @param expression
* @return int
*/
public static int calculate(List<String> expressionList) {
// 创建栈
Stack<String> stack = new Stack<>();
// 遍历
for (String item : expressionList) {
// 使用正则取出数(匹配多位数)
if (item.matches("\\d+")) {
// 入栈
stack.push(item);
} else {
// pop 出两个数,并运算,再入栈
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = 0;
switch (item) {
case Constants.ADD_SYMBOL:
res = num1 + num2;
break;
case Constants.SUB_SYMBOL:
res = num1 - num2;
break;
case Constants.MUL_SYMBOL:
res = num1 * num2;
break;
case Constants.DIV_SYMBOL:
res = num1 / num2;
break;
default:
throw new RuntimeException("运算符有误");
}
// 把 res 入栈
stack.push("" + res);
}
}
// 最后留在 stack 中的数据是运算结果
return Integer.parseInt(stack.pop());
}
}
编写测试方法。
public class Test {
public static void main(String[] args) {
String expression = "1+((2+3)*4)-5";
List<String> infixExpressionToList = ReversePolandCalculator.infixExpressionToList(expression);
System.out.println("中缀表达式对应的 List=" + infixExpressionToList);
List<String> suffixExpressionList = ReversePolandCalculator.infixToSuffixExpressionList(infixExpressionToList);
System.out.println("后缀表达式对应的 List=" + suffixExpressionList);
// 计算表达式的值
System.out.printf("expression=%d", ReversePolandCalculator.calculate(suffixExpressionList));
}
}
输出结果:
中缀表达式对应的 List=[1, +, (, (, 2, +, 3, ), *, 4, ), -, 5]
不存在该运算符
不存在该运算符
后缀表达式对应的 List=[1, 2, 3, +, 4, *, +, 5, -]
expression=16
基本上一个基于栈的逆波兰计算器就实现了。下面给出完整的逆波兰计算器代码,不仅支持多位数,还支持小数。还可以处理任何空白字符串,包括空格、制表符、换页符。
逆波兰计算器完成版
创建一个常量类
public class Constants {
public static final String LEFT = "(";
public static final String RIGHT = ")";
public static final String ADD = "+";
public static final String MINUS = "-";
public static final String TIMES = "*";
public static final String DIVISION = "/";
/**
* 运算符级别: + -
*/
public static final int LEVEL_1 = 1;
/**
* 运算符级别:* /
*/
public static final int LEVEL_2 = 2;
/**
* int 的 最大值(代表括号的等级)
*/
public static final int LEVEL_HIGH = Integer.MAX_VALUE;
}
提取公共方法
public class CommonUtils {
/**
* TODO 匹配运算符
*/
public static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)";
private static final Pattern PATTERN = Pattern.compile("^[-\\+]?[.\\d]*$");
/**
* TODO 去除所有 空白字符
*
* @param str
* @return java.lang.String
*/
public static String replaceAllBlank(String str) {
// \\s+ 匹配任何空白字符,包括空格、制表符、换页符等等, 等价于[ \f\n\r\t\v]
return str.replaceAll("\\s+", "");
}
/**
* TODO 判断是不是数字
*
* @param str
* @return boolean
*/
public static boolean isNumber(String str) {
return PATTERN.matcher(str).matches();
}
/**
* TODO 判断 是不是 运算符
*
* @param str
* @return boolean
*/
public static boolean isSymbol(String str) {
return str.matches(SYMBOL);
}
/**
* TODO 获取运算等级
*
* @param str
* @return int
*/
public static int getLevel(String str) {
if (Constants.ADD.equals(str) || Constants.MINUS.equals(str)) {
return Constants.LEVEL_1;
} else if (Constants.TIMES.equals(str) || Constants.DIVISION.equals(str)) {
return Constants.LEVEL_2;
}
return Constants.LEVEL_HIGH;
}
}
编写核心的计算方法。
public class Calculator {
/**
* 初始化栈
*/
public static Stack<String> stack = new Stack<>();
/**
* Collections.synchronizedList:集合的线程安全包装方法
*/
public static List<String> data = Collections.synchronizedList(new ArrayList<>());
/**
* TODO 匹配
*
* @param str
* @return java.util.List<java.lang.String>
*/
public static List<String> doMatch(String str) {
if (str == null || "".equals(str.trim())) {
throw new RuntimeException("data is empty");
}
if (!CommonUtils.isNumber(str.charAt(0) + "")) {
throw new RuntimeException("data illegal,start not with a number");
}
str = CommonUtils.replaceAllBlank(str);
String each;
int start = 0;
for (int i = 0; i < str.length(); i++) {
if (CommonUtils.isSymbol(str.charAt(i) + "")) {
each = str.charAt(i) + "";
//栈为空,( 操作符,或者 操作符优先级大于栈顶优先级 && 操作符优先级不是( )的优先级 及 是 ) 不能直接入栈
boolean flag = stack.isEmpty() || Constants.LEFT.equals(each) || ((CommonUtils.getLevel(each) > CommonUtils.getLevel(stack.peek())) && CommonUtils.getLevel(each) < Constants.LEVEL_HIGH);
if (flag) {
stack.push(each);
} else if (!stack.isEmpty() && CommonUtils.getLevel(each) <= CommonUtils.getLevel(stack.peek())) {
//栈非空,操作符优先级小于等于栈顶优先级时出栈入列,直到栈为空,或者遇到了(,最后操作符入栈
while (!stack.isEmpty() && CommonUtils.getLevel(each) <= CommonUtils.getLevel(stack.peek())) {
if (CommonUtils.getLevel(stack.peek()) == Constants.LEVEL_HIGH) {
break;
}
data.add(stack.pop());
}
stack.push(each);
} else if (Constants.RIGHT.equals(each)) {
// ) 操作符,依次出栈入列直到空栈或者遇到了第一个)操作符,此时)出栈
while (!stack.isEmpty() && Constants.LEVEL_HIGH >= CommonUtils.getLevel(stack.peek())) {
if (Constants.LEVEL_HIGH == CommonUtils.getLevel(stack.peek())) {
stack.pop();
break;
}
data.add(stack.pop());
}
}
// 前一个运算符的位置
start = i;
} else if (i == str.length() - 1 || CommonUtils.isSymbol(str.charAt(i + 1) + "")) {
each = start == 0 ? str.substring(start, i + 1) : str.substring(start + 1, i + 1);
if (CommonUtils.isNumber(each)) {
data.add(each);
continue;
}
throw new RuntimeException("data not match number");
}
}
//如果栈里还有元素,此时元素需要依次出栈入列,可以想象栈里剩下栈顶为/,栈底为+,应该依次出栈入列,可以直接翻转整个 stack 添加到队列
Collections.reverse(stack);
data.addAll(new ArrayList<>(stack));
System.out.println(data);
return data;
}
/**
* TODO 算出结果
*
* @param list
* @return java.lang.Double
*/
public static Double calculateResult(List<String> list) {
double res = 0D;
if (list == null || list.isEmpty()) {
return null;
}
if (list.size() == 1) {
System.out.println(list);
res = Double.parseDouble(list.get(0));
return res;
}
ArrayList<String> list1 = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
list1.add(list.get(i));
if (CommonUtils.isSymbol(list.get(i))) {
Double d = doTheMath(list.get(i - 2), list.get(i - 1), list.get(i));
list1.remove(i);
list1.remove(i - 1);
list1.set(i - 2, d + "");
list1.addAll(list.subList(i + 1, list.size()));
break;
}
}
calculateResult(list1);
return res;
}
/**
* TODO 运算
*
* @param str1
* @param str2
* @param symbol
* @return java.lang.Double
*/
public static Double doTheMath(String str1, String str2, String symbol) {
Double result;
switch (symbol) {
case Constants.ADD:
result = new BigDecimal(str1).add(new BigDecimal(str2)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
break;
case Constants.MINUS:
result = new BigDecimal(str1).subtract(new BigDecimal(str2)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
break;
case Constants.TIMES:
result = new BigDecimal(str1).multiply(new BigDecimal(str2)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
break;
case Constants.DIVISION:
result = new BigDecimal(str1).divide(new BigDecimal(str2)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
break;
default:
result = null;
}
return result;
}
}
编写测试类。
public static void main(String[] args) {
String math = "9+(3-1)*3+10/2";
try {
calculateResult(doMatch(math));
} catch (Exception e) {
e.printStackTrace();
}
}
输出结果:
[9, 3, 1, -, 3, *, +, 10, 2, /, +]
[20.0]
逆波兰计算器的实现就到这里结束了,其中存在很多问题。如果想实现一个完整的计算器,还有很多细节需要考虑进去。数据结构也是一步一步学习的。慢慢理解才能变的透彻。
参考资料:
作者:韩顺平
课程:《Java数据结构与算法》