Java实现四则运算表达式求值
前言
最近在复习数据结构与算法,在栈的应用中了解到计算机计算四则运算表达式的算法。
计算机计算四则运算主要分两步:
- 将中缀表达式转化为后缀表达式;
- 将后缀表达式进行运算得出结果。
后缀(逆波兰)表达式
后缀表达式是一种不包含括号,运算符放在两个运算对象的后面的表示法,比如四则运算表达式9+(3-1)*3+10/2,其后缀表达式为9 3 1 - 3 * + 10 2 / +。这是计算机采用的形式,计算过程如下:
9 3 1 - 3 * + 10 2 / +
9 2 3 * + 10 2 / +
9 6 + 10 2 / +
15 10 2 / +
15 5 +
20
方法就是:从左到右,将运算符前面的两个数,分别做为运算数和被运算数,如:3 1 - 就等于(3-1)=2,依次进行计算。
中缀转后缀
怎样将中缀表达式转成后缀表达式?
中缀表达式 “9+(3-1)*3+10/2” 转化为后缀表达式 “9 3 1 - 3 * + 10 2 / +”
数字输出,运算符进栈,括号匹配出栈,栈顶优先级不低出栈
规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出;是开括号‘(’,直接入栈;若是闭括号‘)’,则依次出栈,匹配前一个开括号;若栈顶运算符的优先级大于或等于当前运算符,栈顶出栈。
具体过程:
第一个字符9是数字,输出“9”,后面是符号“+”,入栈;
后面是开括号“(”,直接入栈;
然后是数字3,输出,表达式变为“9 3”,接着是“-”入栈,然后是数字1,输出,表达式为“9 3 1”;
接下来是闭括号“)”,需要去匹配前面的“(”,栈顶依次出栈,并输出。因此”-“出栈并输出,表达式为“9 3 1 -”,“(”出栈;
然后是符号“*”,因为栈顶“+”优先级低于“*”,所以“*”入栈,后面数字3输出,表达式变为“9 3 1 - 3”;
之后是符号“+”,因为栈顶“*”优先级高于“+”,所以栈中元素出栈(因为没有比“+”更低的优先级,所以全部出栈),表达式变为“9 3 1 - 3 * +”,当前符号“+”入栈;
- 接着数字10,输出,表达式变为“9 3 1 - 3 * + 10”;
然后“/”优先级高于栈顶“+”,入栈;
最后是数字2,输出,表达式为“9 3 1 - 3 * + 10 2”;
已经到了最后,所以将栈中符号全部出栈并输出。最终输出后缀表达式为“9 3 1 - 3 * + 10 2 / +”
计算后缀表达式
中缀转后缀是将符号进出栈,计算后缀表达式就需要将数字进出栈。
规则就是将数字依次入栈,遇到运算符就出栈两次,将栈顶元素分别作为被运算数和运算数进行计算,然后将计算结果入栈,直到表达式遍历完成,运算结束,栈中存在的唯一元素就是表达式的值。
代码实现:
import java.util.Scanner;
import java.util.Stack;
public class JavaTest {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String infix = in.nextLine();
in.close();
String postfix = trans2postfix(infix);// 得到后缀表达式
System.out.println("postfix : " + postfix);
int count = calculate(postfix);
System.out.println("count = " + count);
}
// 计算后缀表达式
private static int calculate(String postfix) {
Stack<Integer> stack = new Stack<>();
char[] arr = postfix.toCharArray();
for (int i = 0; i < arr.length; i++) {
char current = arr[i];// 当前字符
if (Character.isDigit(current)) {// 如果是数字,入栈
stack.push(current - 48);
} else {
int n1 = stack.pop();
int n2 = stack.pop();
int m = 0;
switch (current) {
case '+':
m = n2 + n1;
break;
case '-':
m = n2 - n1;
break;
case '*':
m = n2 * n1;
break;
case '/':
m = n2 / n1;
break;
default:
break;
}
stack.push(m);
}
}
return stack.get(0);
}
// 中缀转后缀
public static String trans2postfix(String infix) {
StringBuilder sb = new StringBuilder();
Stack<Character> stack = new Stack<>();
char[] arr = infix.toCharArray();
for (int i = 0; i < arr.length; i++) {
char current = arr[i];// 当前字符
if (Character.isDigit(current)) {// 如果是数字
sb.append(current);
} else {
if (stack.isEmpty()) {// 如果栈为空,直接入栈
stack.push(current);
} else {
if (current == ')') {// 闭括号
while (true) {
char top = stack.pop();
if (top != '(') {
sb.append(top);
} else {
break;
}
}
} else {
if (current != '(') {// 开括号,直接入栈
while (comparePriority(current, stack.peek()) <= 0) {// 当前优先级不比栈顶高,出栈
char top = stack.pop();
sb.append(top);
if (stack.isEmpty()) {
break;
}
}
}
stack.push(current);
}
}
}
}
while (stack.size() != 0) {
sb.append(stack.pop());
}
return sb.toString();
}
// 比较运算符优先级
private static int comparePriority(char arg0, char arg1) {
int pri0 = getPriority(arg0);
int pri1 = getPriority(arg1);
if (pri0 < pri1) {
return 1;
} else if (pri0 == pri1) {
return 0;
} else {
return -1;
}
}
// 获取运算符优先级
private static int getPriority(char arg0) {
switch (arg0) {
case '*':
case '/':
return 3;
case '+':
case '-':
return 4;
default:
return 0xF;
}
}
}
运行结果:
实现过程中遇到一个问题,使用char字符会将整数切成10以内的数字,比如10会变成1和0,而且后缀表达式也没有做字符间隔,比如931无法区别是9、3、1还是93、1或者931,所以上面的代码只能计算10以内的四则运算。优化可以将char改成int,并且输出要加上间隔。
能力有限,如有不足,请多指教。