最近,在数据结构课上学习到了后缀表达式。于是决定重写之前做过的计算器APP的算法。(检测表达式合法性的是另外的算法,这里暂时不提及)首先我简单地对比这两种算法:
一、中缀表达式。
1.同时在两边遍历表达式,储存最里层的括号的位置a,b,和括号的对数n。
2.将表达式按括号的位置裁剪出一个子表达,substring(a+1, b)。
3.遍历子表达式,就将从开始位置0到运算符位置k剪切下来就是一个操作数字符串substring(0, k)。转化为double类型后存入一个链表numList中,将运算符存在charList中。起始位置改为运算符的下一位继续,直到等于号的出现。
4.如下图所示,下面是两个LinkedList,先计算乘号和除号,将与运算符对应位置与前一个位置的数字进行运算。如下计算乘法,2*3=6将结果6写到原来2的位置上,此时3和乘号要从链表里面删去。经过两次遍历(先乘除后加减),最后numList只剩最终结果,charList只剩等于号。
5.将结果数字转换为字符串后取代原来在括号中的表达式。循环n+1次运算会得到最终结果。
二、后缀表达式。
1.遍历表达式,数字直接切下加到另外的StringBuilder里,而若为 '(',入栈;
2.若为 ')',则依次把栈中的的运算符加入后缀表达式中,直到出现'(',从栈中删除'(' ;
3.若为除括号外的其他运算符, 当其优先级高于除'('以外的栈顶运算符时,直接入栈。否则从栈顶开始,依次弹出比当前处理的运算符优先级高和优先级相等的运算符,直到一个比它优先级低的或者遇到了一个左括号为止。(构造方法中,定义了运算符的优先级)
public Caculate(String exp) {
this.exp = exp;
stBuilder = new StringBuilder();
stack = new Stack<String>();
map = new HashMap<String, Integer>();
map.put("=", 0);
map.put("+", 1);
map.put("-", 1);
map.put("×", 2);
map.put("÷", 2);
map.put("(", 3);
map.put(")", 3);
}
4.当扫描的中缀表达式结束时,栈中的的所有运算符出栈;
(后缀表达式的转化完成,注意新的Sting里每个后面都加了空格方便后面的计算)
public String convertToRPN() {
int start = 0;
for (int i = 0; i < exp.length(); i++) {
if (exp.charAt(i)=='='
||exp.charAt(i)=='-'
||exp.charAt(i)=='+'
||exp.charAt(i)=='×'
||exp.charAt(i)=='÷'
||exp.charAt(i)=='('
||exp.charAt(i)==')') {
if (exp.charAt(start)>='0'&&exp.charAt(start)<='9') {
stBuilder.append(exp.substring(start, i) + " ");
}
start = i + 1;
putIntoStack(String.valueOf(exp.charAt(i)));
}
}
if (!stack.empty()) {
stBuilder.append(stack.pop());
}
return stBuilder.toString();
}
private void putIntoStack(String operation) {
System.out.println(operation);
while (!stack.empty()) {
String top = stack.peek();
// System.out.println("top " + top);
if (operation.equals("(")) {
stack.push(operation);
break;
} else if (operation.equals(")")) {
while (!top.equals("(")) {
stBuilder.append(top + " ");
System.out.println("test1 " + stack.pop());
top = stack.peek();
System.out.println("test2 " + top);
}
stack.pop();
break;
} else if (top.equals("(")) {
stack.push(operation);
break;
} else if(map.get(top) >= map.get(operation)) {
stBuilder.append(top + " ");
stack.pop();
} else {
stack.push(operation);
break;
}
}
if (stack.empty()) {
stack.push(operation);
}
System.out.println(stack.toString());
}
5.遍历后缀表达式,数字入栈,遇到运算符弹出两个数字计算再压入栈中,最后剩下来的就是运算结果。显然后缀表达式的方法思路更清晰。
public String caculate(String exp) {
Stack<Double> stack = new Stack<Double>();
for (String s : exp.split(" ")) {
if (s.charAt(0) >= '0' && s.charAt(0) <= '9') {
stack.push(Double.parseDouble(s));
} else {
BigDecimal a, b, d;
switch (s.charAt(0)) {
case '+':
a = BigDecimal.valueOf(stack.pop());
b = BigDecimal.valueOf(stack.pop());
d = a.add(b);
stack.push(d.doubleValue());
break;
case '-':
a = BigDecimal.valueOf(stack.pop());
b = BigDecimal.valueOf(stack.pop());
d = b.subtract(a);
stack.push(d.doubleValue());
break;
case '×':
a = BigDecimal.valueOf(stack.pop());
b = BigDecimal.valueOf(stack.pop());
d = a.multiply(b);
stack.push(d.doubleValue());
break;
case '÷':
a = BigDecimal.valueOf(stack.pop());
b = BigDecimal.valueOf(stack.pop());
d = b.divide(a, 16, BigDecimal.ROUND_HALF_DOWN);
stack.push(d.doubleValue());
break;
}
}
}
return String.valueOf(stack.pop());
}
(因为
java
的基本数据类型的浮点型运算会有误差,在商业计算中使用
BigDecimal
类运算,
BigDecimal
类封装了误差纠正的方法。看这解释)