简单计算器实现
目录
中缀表达式
先说一下中缀表达式,平时我们使用的运算表达式就是中缀表达式,例如1+3*2,中缀表达式的特点就是:二元运算符总是置于与之相关的两个运算对象之间
人读起来比较好理解,但是计算机处理起来就很麻烦,运算顺序往往因表达式的内容而定,不具规律性
后缀表达式
后缀表达式,后缀表达式的特点就是:每一运算符都置于其运算对象之后,以上面的中缀表达式1+2*3为例子,转为后缀表达式就是123*+
中缀表达式转换为后缀表达式
下面先分析怎么把中缀表达式转换为后缀表达式,这里我们考虑六种操作符'+'、'-'、'*'、'/'、'('、')'。
完成中缀转后缀我们需要两个数组,都以栈的方式来操作,一个数组用来存放后缀表达式(char num[100]),
一个数组用来临时存放操作数(char opera[100])(这里说临时存放,是因为最后都要入栈到后缀表达式数组num中,这个数组就相当于一个中转站)
1、从左往右扫描中缀表达式(这里我们以1*(2+3)为例)
2、如果是数字那么将其直接入栈到数组num中
3、如果是操作数,需要进一步判断
(1)如果是左括号'('直接入栈到数组opera中
(2)如果是运算符('+'、'-'、'*'、'/'),先判断数组opera的栈顶的操作数的优先级(如果是空栈那么直接入栈到数组opera),如果是左括号那么直接入栈到数组opera中。
如果栈顶是运算符,且栈顶运算符的优先级大于该运算符,那么将栈顶的运算符出栈,并入栈到数组num中,重复步骤3。
如果栈顶运算符优先级小于该运算符,那么直接将该运算符入栈到opera中
(3)如果是右括号')',那么说明在opera数组中一定有一个左括号与之对应(在你没输错的情况下),那么将opera中的运算符依次出栈,并入栈到num中,直到遇到左括号'('(注意左括号不用入栈到num)
4、如果中缀表达式扫描完了,那么将opera中的操作数依次出栈,并入栈到num中就可以了,如果没有没有扫描完重复1-3步
上面就是中缀表达式转后缀表达式的步骤了,下面用图来直观的了解一下这个过程
后缀表达式的计算
完成了中缀表达式转后缀表达式,接下来就是后缀表达式的计算了,后缀表达式的计算比中缀转后缀要稍微简单一点,只需要对我们转换好的后缀表达式从左往右依次扫描,并依次入栈就行了,意思是只需要用一个数组(double num[100])就OK了
需要考虑的情况如下
1、如果是数字,那么直接入栈到num中
2、如果是运算符,将栈顶的两个数字出栈(因为我们考虑的运算符加、减、乘、除都是双目运算符,只需要两个操作数),出栈后对两个数字进行相应的运算,并将运算结果入栈
3、直到遇到'\0'
下面用几张图,来直观了解下这个过程,以上面转换好的后缀表达式"123+*"为例(这里用ss来存储后缀表达式,num来存储计算结果,注意不要与上面图中num搞混淆了)
中缀转后缀代码
private static List<String> parseToSuffixExpression(List<String> expressionList) {
//创建一个栈用于保存操作符
Stack<String> opStack = new Stack<>();
//创建一个list用于保存后缀表达式
List<String> suffixList = new ArrayList<>();
for(String item : expressionList){
//得到数或操作符
if(isOperator(item)){
//是操作符 判断操作符栈是否为空
if(opStack.isEmpty() || "(".equals(opStack.peek()) || priority(item) > priority(opStack.peek())){
//为空或者栈顶元素为左括号或者当前操作符大于栈顶操作符直接压栈
opStack.push(item);
}else {
//否则将栈中元素出栈如队,直到遇到大于当前操作符或者遇到左括号时
while (!opStack.isEmpty() && !"(".equals(opStack.peek())){
if(priority(item) <= priority(opStack.peek())){
suffixList.add(opStack.pop());
}
}
//当前操作符压栈
opStack.push(item);
}
}else if(isNumber(item)){
//是数字则直接入队
suffixList.add(item);
}else if("(".equals(item)){
//是左括号,压栈
opStack.push(item);
}else if(")".equals(item)){
//是右括号 ,将栈中元素弹出入队,直到遇到左括号,左括号出栈,但不入队
while (!opStack.isEmpty()){
if("(".equals(opStack.peek())){
opStack.pop();
break;
}else {
suffixList.add(opStack.pop());
}
}
}else {
throw new RuntimeException("有非法字符!");
}
}
//循环完毕,如果操作符栈中元素不为空,将栈中元素出栈入队
while (!opStack.isEmpty()){
suffixList.add(opStack.pop());
}
return suffixList;
}
/**
* 判断字符串是否为操作符
* @param op
* @return
*/
public static boolean isOperator(String op){
return op.equals("+") || op.equals("-") || op.equals("*") || op.equals("/");
}
/**
* 判断是否为数字
* @param num
* @return
*/
public static boolean isNumber(String num){
return num.matches("\\d+");
}
/**
* 获取操作符的优先级
* @param op
* @return
*/
public static int priority(String op){
if(op.equals("*") || op.equals("/")){
return 1;
}else if(op.equals("+") || op.equals("-")){
return 0;
}
return -1;
}
原中缀表达式字符串转换为list结构代码
/**
* 将表达式转为list
* @param expression
* @return
*/
private static List<String> expressionToList(String expression) {
int index = 0;
List<String> list = new ArrayList<>();
do{
char ch = expression.charAt(index);
if(ch < 47 || ch > 58){
//是操作符,直接添加至list中
index ++ ;
list.add(ch+"");
}else if(ch >= 47 && ch <= 58){
//是数字,判断多位数的情况
String str = "";
while (index < expression.length() && expression.charAt(index) >=47 && expression.charAt(index) <= 58){
str += expression.charAt(index);
index ++;
}
list.add(str);
}
}while (index < expression.length());
return list;
}
后缀表达式求值代码
/**
* 根据后缀表达式list计算结果
* @param list
* @return
*/
private static int calculate(List<String> list) {
Stack<Integer> stack = new Stack<>();
for(int i=0; i<list.size(); i++){
String item = list.get(i);
if(item.matches("\\d+")){
//是数字
stack.push(Integer.parseInt(item));
}else {
//是操作符,取出栈顶两个元素
int num2 = stack.pop();
int num1 = stack.pop();
int res = 0;
if(item.equals("+")){
res = num1 + num2;
}else if(item.equals("-")){
res = num1 - num2;
}else if(item.equals("*")){
res = num1 * num2;
}else if(item.equals("/")){
res = num1 / num2;
}else {
throw new RuntimeException("运算符错误!");
}
stack.push(res);
}
}
return stack.pop();
}