问题描述:输入一个缺少左括号的表达式,打印出其补全括号的中序表达式,并计算表达式的值。例如给定表达式:
1+2) * 3-4) * 5-6)))
得到输出:
((1+2) * ((3-4) * (5-6)))
计算得到其值为3
问题分析:计算表达式的问题通常使用堆栈。有的算法中利用操作符的优先级以及复杂的记录过程,其实只要利用好堆栈的特性,任何多余的操作都是完全没有必要的。这里首先考虑计算出表达式的值的问题,以此思路推及补全表达式的问题。
由于所计算的表达式是由数字和操作符构成的,因而解析表达式的时候需要使用两个堆栈分别存放数字和操作符。将表达式分别入栈,每当遇到右括号时出栈计算子表达式,将结果再次入栈参与下一次的计算。
public class Test1{
public static boolean isNumber(char s) {
return (48<=s&&s<=57)?true:false;
}
public static boolean isOpr(char s) {
return (s=='+'||s=='-'||s=='*'||s=='/')?true:false;
}
public static void main(String[] args) {
In in=new In("D:\\expression.txt");
String string=in.readAll();
char[] word=string.toCharArray();
ListStack<Character> oprStack=new ListStack<>();// 数字
ListStack<Integer> numStack=new ListStack<>();// 操作符
for (int i = 0; i < word.length; i++) {
if (isNumber(word[i])) {// 数字
numStack.push(word[i]-48);
}else if (isOpr(word[i])) {// 操作符
oprStack.push(word[i]);
}else if (word[i]==')') {
// 遇到右括号的时候计算子表达式
int right=numStack.pop();
int left=numStack.pop();
char opr=oprStack.pop();
int result=0;
switch (opr) {
case '+':
result=left+right;
break;
case '-':
result=left-right;
break;
case '*':
result=left*right;
break;
case '/':
result=left/right;
break;
default:
break;
}
// 最后将子表达式的结果入栈参与下次计算
numStack.push(result);
}
}
// 程序最后操作符的堆栈为空,数字的堆栈中就是最后的计算结果
System.out.println(numStack.pop());
}
}
可见整个算法是非常简单的。接着要将表达式的左括号补全,应该如何做呢?首先会想到要去找插入左括号的位置,而左括号又取决于右括号的迭代层次,因此要想通过记录位置的方法来解决此问题不是很容易。
而通过分析计算表达式的值的问题,我们可以得到启发:通过将子表达式的结果逐层代入父表达式,就可以利用堆栈非常简单的解决问题,从而有以下解决方案。
public class Test2{
public static void main(String[] args) {
In in=new In("D:\\expression.txt");
String string=in.readAll();
String[] strings =string.split("");
Stack<String> exprStack=new Stack<>();
for (int i = 0; i < strings.length; i++) {
// 将表达式内容全部入栈
if (!strings[i].equals(")")) {
exprStack.push(strings[i]);
}else {
// 在遇到右括号的时候将子表达式用一个String对象表示并入栈以代入父表达式
String right=exprStack.pop();
String opr=exprStack.pop();
String left=exprStack.pop();
String expr="("+left+opr+right+")";// 补全左右括号
exprStack.push(expr);
}
}
System.out.println(exprStack.pop());
}
}
上述算法关键在于将子表达式用一个String对象表示,就如同一个计算的结果,入栈后代入父表达式。
总结:将子表达式用一个结果表示,逐层代入父表达式,可以利用堆栈处理表达式的补全问题。