最近,在数据结构课上学习到了后缀表达式。于是决定重写之前做过的计算器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只剩等于号。

 

java 计算器 swing组件 java计算器算法_后缀表达式

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 类封装了误差纠正的方法。看这解释)