四则运算的基本方法是使用双栈实现中缀表达式转后缀表达式、后缀表达式的计算结合,然而对于带有小数及复杂括号嵌套的式子而言仅是如此远远不够,因为算式的初始形态是字符串,计算机并不认识字符串中的算术运算组成,例如"3.1415*(1.221-(6.487+3.223)/(9.145-5.34)+2.27)-(7.18+8.24)*0.15"
这个字符串中,计算机无法自主的剥离哪些是操作数哪些是运算符。
笔者的做法是首先定义算术单元类(ArithmeticUnit)
用于表示一个算术单元,之后将原始式子字符串转换为算术单元串,最后使用此算术单元串进行中缀表达式的计算。笔者将式子中的组分分为操作数单元和非操作数单元,算术单元类中定义了一些方法可以判断算术单元的具体类型,根据具体的类型可以进行相应的转换操作,例如将操作数的字符串转换为其对应的数值。算术单元的具体类型可分为:
操作数单元
非操作数单元:+,-,*,/和(,)
算术单元类中应该有的接口例如:
/* 判断本算术单元是否为数值 */
public boolean isNumber();
/* 判断本算术单元是否为左括号 */
public boolean isLeftBracket();
/* 判断本算术单元是否为右括号 */
public boolean isRightBracket();
/* 判断本算术单元是否为运算符 */
public boolean isOperator();
/* 如果本算术单元是运算符,判断算术优先级是否低于指定的运算符 */
public boolean isPriorityLowerThan(ArithmeticUnit operator);
/* 如果本算数单元是操作数,使用此方法可获得对应的数值 */
public double getNumber();
/* 如果本算术单元是运算符,可以使用此方法对传入的两个操作数进行相应运算并取得运算结果 */
public ArithmeticUnit operate(ArithmeticUnit opr1,ArithmeticUnit opr2);
所谓的算术单元串
即ArithmeticUnit
对象连接而成的线性数据结构,代码中笔者使用链式队列代表算术单元串。中缀表达式的计算中从左到右依次扫描算式,笔者的代码中则是从队首到队尾依次扫描算术单元节点。从原始式子字符串到算术单元串的转换过程中最不容易的就是如何识别并转换操作数,因为操作数可能有很多位数组成。对于一个合法的式子,首尾的算术单元是操作数(实际并不一定,但使用这种判断方式再添加细节有利于运算),中间的夹在两个非操作数算术单元之间的为操作数,根据这一原理可使用队列暂存还不完整的操作数字符组分,过程如:
1.扫描到字符串中的数字字符('0'~'9'和"."),将其入队
2.扫描到非数字字符:
(1)依次出队并拼接组成字符串,组成的字符串即为操作数字符串,使用此字符串创建操作数算术单元并入算术单元串队列
(2)如果此非数字字符为空格,继续向下扫描
(3)如果不是空格,使用此字符创建非操作数算术单元并入算术单元串队列
提前应考虑一种特殊情况,式子以"+"
或"-"
开头依然是一个合法的算式,但却不利于上述的转换步骤,很简单的解决办法就是将原算式拼接到字符串"0"
之后,既可解决问题又不影响运算结果:
if(formula.chaAt(0) == '+' || formula.chaAt(0) == '-') {
formula = "0" + formula;
}
根据上述步骤即可完成原始式子字符串向算术单元字符串的转换,中缀表达式的计算过程笔者不再赘述,详细代码可见后文,笔者的方法是定义一个算数式子类(ArithmeticFormula
),其核心属性即为字符串形式的算术式子和算术单元串形式的算术式子,笔者将转换过程置于构造器中完成,因此代码看上去有些臃肿,另外笔者将整个过程分为式子的形式转换与中缀表达式的计算两个步骤,实际上可以合成一个步骤使得时间复杂度降低一半,但代价是更加臃肿复杂的代码。
先上测试结果(详细源码见下文):
测试代码:
package com.coddffee.datastructure;
import com.coddffee.datastructure.algorithm.ArithmeticFormula;
import java.util.Scanner;
public class App {
public static void main( String[] args ) {
ArithmeticFormula arithmeticFormula;
String message;
Scanner scanner = new Scanner(System.in);
System.out.println("input a formula : ");
while (true) {
/**
* 阻塞式读取控制台输入
*/
message = scanner.nextLine();
/**
* 如果输入"exit"命令,结束读取控制台输入
*/
if(message.contains("exit")) {
System.out.println("bye~");
return;
}
else {
/**
* 如果输入合法式子,打印所表示运算的结果
*/
arithmeticFormula = new ArithmeticFormula(message);
System.out.println(message + " = " + arithmeticFormula.calculate());
}
}
}
}
链式队列类:
package com.coddffee.datastructure.structures;
/**
* 泛型链式队列
*/
public class LinkQueue<T> {
private LinkQueueNode front;
private LinkQueueNode rear;
public LinkQueue() {
front = null;
rear = null;
}
/**
* 入队
*/
public void enter(T item) {
LinkQueueNode node = new LinkQueueNode();
node.data = item;
if(front == null) {
front = node;
rear = node;
}
else rear.next = node;
rear = node;
rear.next = null;
}
/**
* 出队
*/
public T leave() {
if(isEmpty()) throw new NullPointerException("queue null.");
LinkQueueNode node = front;
front = node.next;
return node.data;
}
/**
* 只读队首
*/
public T peek() {
if(isEmpty()) throw new NullPointerException("queue null.");
return front.data;
}
/**
* 判空
*/
public boolean isEmpty() {
return front == null;
}
/**
* 内部类用于表示节点
*/
private class LinkQueueNode {
T data;
LinkQueueNode next;
}
@Override
public String toString() {
if(isEmpty()) throw new NullPointerException("queue null.");
String string = "";
LinkQueueNode node = front;
while (node != null) {
string += (node.data.toString() + " ");
node = node.next;
}
return string;
}
}
链栈类:
package com.coddffee.datastructure.structures;
/**
* 泛型链栈
*/
public class LinkStack<T> {
/**
* 带头节点
*/
private LinkStackNode head;
public LinkStack() {
head = new LinkStackNode(null,null);
}
/**
* 入栈
*/
public void push(T item) {
LinkStackNode node = new LinkStackNode(item,head.next);
head.next = node;
}
/**
* 出栈
*/
public T pop() {
if(isEmpty()) throw new NullPointerException("stack null.");
T data = head.next.data;
head.next = head.next.next;
return data;
}
/**
* 只读栈顶
*/
public T peek() {
if(isEmpty()) throw new NullPointerException("stack null.");
return head.next.data;
}
/**
* 判空
*/
public boolean isEmpty() {
return head.next == null;
}
/**
* 内部类用于表示节点
*/
private class LinkStackNode {
T data;
LinkStackNode next;
LinkStackNode(T data,LinkStackNode next) {
this.data = data;
this.next = next;
}
}
@Override
public String toString() {
if(isEmpty()) throw new NullPointerException("stack null.");
String string = "";
LinkStackNode node = head.next;
while (node != null) {
string += node.data.toString() + " ";
node = node.next;
}
return string;
}
}
算术节点类:
package com.coddffee.datastructure.algorithm;
/**
* 用于表示算式中的一个运算节点,例如"3.14+(2.15-3.33)/2.1"中的运算节点有:
* 3.14 , + , ( , 2.15 , - , 3.33 , ) , / , 2.1
*/
public class ArithmeticUnit {
/**
* 核心属性:单元字符串,可能是数字、"+","-","*","/","(",")"
*/
private String unit;
public ArithmeticUnit() {super();}
/**
* 通过字符串创建算术节点,适用于操作数节点
*/
public ArithmeticUnit(String unit) {
this.unit = unit;
}
/**
* 通过字符创建算术节点,适用于非操作数节点,即运算符或括号
*/
public ArithmeticUnit(Character unit) {
if(unit != '+' && unit != '-' && unit != '*' && unit != '/' && unit != '(' && unit != ')')
throw new RuntimeException("invalid constructor of " + unit);
this.unit = unit + "";
}
public void setUnit(String unit) {
this.unit = unit;
}
public String getUnit() {
return unit;
}
/**
* 判断是否为操作数算术节点
*/
public boolean isNumber() {
return !isOperator() && !isBracket();
}
/**
* 判断是否为括号
*/
public boolean isBracket() {
return isLeftBracket() || isRightBracket();
}
/**
* 判断是否为运算符
*/
public boolean isOperator() {
return unit.equals("+") || unit.equals("-") || unit.equals("*") || unit.equals("/");
}
/**
* 判断是否为左括号
*/
public boolean isLeftBracket() {
return unit.equals("(");
}
/**
* 判断是否为右括号
*/
boolean isRightBracket() {
return unit.equals(")");
}
/**
* 如果是操作数算术节点,获取数值
*/
public double getNumber() {
if(!isNumber()) throw new RuntimeException("unknown number : " + unit);
return Double.parseDouble(unit.trim());
}
/**
* 如果是括号算术节点,获取具体字符
*/
public char getBracket() {
if(!isBracket()) throw new RuntimeException("unknown bracket : " + unit);
return unit.charAt(0);
}
/**
* 如果是运算符算术节点,获取具体值
*/
public char getOperator() {
if(!isOperator()) throw new RuntimeException("unknown operator : " + unit);
return unit.charAt(0);
}
/**
* 工具方法,获取运算符的算术优先级
*/
private static int priorityOf(ArithmeticUnit unit) {
if(unit.getOperator() == '+' || unit.getOperator() == '-') return 0;
else if(unit.getOperator() == '*' || unit.getOperator() == '/') return 1;
throw new RuntimeException("unknown operator : " + unit);
}
/**
* 如果当前算术节点是运算符,判断自身的算术优先级是否低于传入运算符算术节点
*/
public boolean priorityIsLowerThan(ArithmeticUnit unit) {
return priorityOf(this) < priorityOf(unit);
}
/**
* 如果当前算术节点是运算符,使用此方法可进行相应的算术运算
*/
public ArithmeticUnit operate(ArithmeticUnit leftNumber,ArithmeticUnit rightNumber) {
if(unit.equals("+")) return new ArithmeticUnit(leftNumber.getNumber() + rightNumber.getNumber()+"");
else if(unit.equals("-")) return new ArithmeticUnit(leftNumber.getNumber() - rightNumber.getNumber()+"");
else if(unit.equals("*")) return new ArithmeticUnit(leftNumber.getNumber() * rightNumber.getNumber()+"");
else if(unit.equals("/")) return new ArithmeticUnit(leftNumber.getNumber() / rightNumber.getNumber()+"");
else throw new RuntimeException("unknown operator : " + unit);
}
@Override
public String toString() {
return unit;
}
}
算术式子类:
package com.coddffee.datastructure.algorithm;
import com.coddffee.datastructure.structures.LinkQueue;
import com.coddffee.datastructure.structures.LinkStack;
/**
* 用于表示算术式子,将式子的字符串形式转换为算术单元(ArithmeticUnit)表示的串
*/
public class ArithmeticFormula {
/**
* 式子的字符串形式
*/
private String formula;
/**
* 式子的算术单元串形式
*/
private LinkQueue<ArithmeticUnit> arithmeticUnits;
/**
* 工具方法,判断扫描到的字符是否属于操作数的组成部分
*/
private static boolean checkNumberPart(char part) {
return part != '+' && part != '-' && part != '*' && part != '/' && part != '(' && part != ')';
}
/**
* 工具方法,判断扫描到的字符是否是空格
*/
private static boolean checkSpace(char part) {
return part == ' ';
}
public String getFormula() {return this.formula;}
/**
* 对式子做初步整理后转换为算术单元串
*/
public ArithmeticFormula(String formula) {
this.formula = formula;
char first = formula.charAt(0);
/**
* 如果式子以"+"或"/"开头将对中缀表达式的计算造成不利,
* 在开头添加"0"可解决这一问题并且不影响运算结果
*/
if(first == '+' || first == '-') {
this.formula = "0" + formula;
}
arithmeticUnits = new LinkQueue<>();
/**
* 创建队列用于保存属于操作数部分的字符
*/
LinkQueue<Character> numberCharacters = new LinkQueue<>();
String numberString = "";
/**
* 逐个扫描字符
*/
for(int i=0;i<this.formula.length();i++) {
Character character = this.formula.charAt(i);
/**
* 如果当前字符是操作数的一部分,先将其暂存于队列
*/
if(checkNumberPart(character)) numberCharacters.enter(character);
/**
* 如果当前字符是空格,继续下一次扫描
*/
else if(checkSpace(character)) continue;
/**
* 如果当前字符是运算符或括号,说明numberCharacters队列中存了一个完整的操作数,
* 依次出队组成操作数的字符串
*/
else {
while(true) {
if(numberCharacters.isEmpty()) break;
numberString += numberCharacters.leave();
}
if(numberString != "") {
/**
* 创建操作数算术节点
*/
arithmeticUnits.enter(new ArithmeticUnit(numberString));
numberString = "";
}
/**
* 存储非操作数节点
*/
arithmeticUnits.enter(new ArithmeticUnit(character));
}
}
/**
* 将最后一个操作数取出(如果存在)
*/
while(true) {
if(numberCharacters.isEmpty()) break;
numberString += numberCharacters.leave();
}
if(!numberString.equals("")) arithmeticUnits.enter(new ArithmeticUnit(numberString));
}
/**
* 运算方法,执行式子表示的算术运算
*/
public double calculate() {
/**
* 运算符算术节点栈
*/
LinkStack<ArithmeticUnit> operatorStack = new LinkStack<>();
/**
* 操作数算术节点栈
*/
LinkStack<ArithmeticUnit> numberStack = new LinkStack<>();
/**
* 逐个节点的扫描转换之后的算术节点串
*/
while(true) {
if(arithmeticUnits.isEmpty()) break;
ArithmeticUnit unit = arithmeticUnits.leave();
/**
* 扫描到操作数节点直接入操作数栈
*/
if(unit.isNumber()) numberStack.push(unit);
/**
* 扫描到左括号算术节点直接入操作符栈
*/
else if(unit.isLeftBracket()) operatorStack.push(unit);
/**
* 扫描到右括号节点时开始回溯计算
*/
else if(unit.isRightBracket()) {
while (true) {
/**
* 回扫到左括号节点时退出回溯
*/
if(operatorStack.peek().isLeftBracket()) break;
/**
* 取出一个操作符和两个操作数进行相应的运算
*/
ArithmeticUnit operator = operatorStack.pop();
ArithmeticUnit opr2 = numberStack.pop();
ArithmeticUnit opr1 = numberStack.pop();
/**
* 运算结果压入操作数栈
*/
ArithmeticUnit value = operator.operate(opr1,opr2);
numberStack.push(value);
}
/**
* 将左括号节点弹出
*/
operatorStack.pop();
}
/**
* 扫描到运算符节点
*/
else if(unit.isOperator()) {
/**
* 如果运算符栈空、运算符栈顶为"("、运算符栈顶运算符优先级更低,直接入栈
*/
if(operatorStack.isEmpty()) operatorStack.push(unit);
else if(operatorStack.peek().isLeftBracket()) operatorStack.push(unit);
else if(operatorStack.peek().priorityIsLowerThan(unit)) operatorStack.push(unit);
/**
* 如果运算符栈顶运算符优先级高于或等于当前运算符,开始回溯计算
*/
else if(!operatorStack.peek().priorityIsLowerThan(unit)) {
while (true) {
/**
* 当运算符栈空、扫描到左括号节点、扫描到优先级更低的运算符节点时退出回溯
*/
if(operatorStack.isEmpty()) break;
else if(operatorStack.peek().isLeftBracket()) break;
else if(operatorStack.peek().priorityIsLowerThan(unit)) break;
ArithmeticUnit operator = operatorStack.pop();
ArithmeticUnit opr2 = numberStack.pop();
ArithmeticUnit opr1 = numberStack.pop();
/**
* 进行相应的运算并将结果压入操作数栈顶
*/
ArithmeticUnit value = operator.operate(opr1,opr2);
numberStack.push(value);
}
/**
* 将当前运算符节点压入运算符栈中
*/
operatorStack.push(unit);
}
}
}
/**
* 处理剩余的算术节点
*/
while (true) {
if(operatorStack.isEmpty()) break;
ArithmeticUnit operator = operatorStack.pop();
ArithmeticUnit opr2 = numberStack.pop();
ArithmeticUnit opr1 = numberStack.pop();
ArithmeticUnit value = operator.operate(opr1,opr2);
numberStack.push(value);
}
/**
* 最终操作数栈中将有且仅有一个节点,即为最终运算结果
*/
return numberStack.peek().getNumber();
}
public LinkQueue<ArithmeticUnit> getArithmeticUnits() {
return arithmeticUnits;
}
}