表达式
由数字、算符、数字分组符号(括号)、自由变量和约束变量等以能求得数值的有意义排列方法所得的组合。约束变量在表达式中已被指定数值,而自由变量则可以在表达式之外另行指定数值。(百度百科)
前缀表达式
也叫“波兰式”,没有括号的算术表达式。这种表达式将运算符写在前面,操作数写在后面。
举例:- a + b c 等价于 a - ( b + c )
中缀表达式
标准的表达式(多项式写法),是一种通用的算术或逻辑公式的表达方法。这种表达式就是平时所见的表达式的正常表达。
举例:a + b * c + ( d * e + f ) * g
后缀表达式
缀表达式的后缀记法,也叫逆波兰记法。这种表达式不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。
后缀表达式举例:a b c * + d e * f + g * + 等价于 a + b * c + ( d * e + f ) * g
前缀,中缀,后缀表达式区分
运算符与操作符的位置不同。前缀表达式操作符位于其相关操作数前,后缀表达式操作符位于其相关操作数之后,中缀表达式操作符位于其相关操作数中间。前缀表达式和后缀表达式不需要括号来表示其运算优先级。
中缀表达式方便于正常的观看和优先级计算,而计算机闭关没有运算符的优先级计算机制,而是按照表达式从左向右进行计算。相对于中缀表达式来说,计算机操作前缀表达式或后缀表达式更加方便。
本节将分析如何做中缀表达式到后缀表达式的转换。
如何将中缀表达式转换为后缀表达式呢?首先需要先理解中缀表达式的运算逻辑。
中缀运算逻辑
计算表达式:a + b + c
嗯…这个就不用说了,不论是自己计算还是计算机,直接从左到右计算即可
计算表达式:a * b * c
同样,从左到右依次计算
计算表达式:a * b + c
乘法的优先级高于加法,先算乘法,后算加法。
① 计算 a * b 记为 A
② 计算 A + c
虽然乘法的优先级高于加法,但由于该表达式 * 在前,+ 在后,所以看起来依然是从左向右依次计算
计算表达式: a + b * c
① 计算表达式 b * c,将其结果存为 A
② 计算表达式 a + A,得到最终结果
通过观察可以发现,明明是 + 在前,但由于 * 的优先级高于 +,还是先计算了 乘法。
中缀表达式转后缀表达式思想:
利用栈的LIFO(后进先出)的特性,调整表达式的运算顺序。具体操作为:依次读取中缀表达式的操作数和操作符,如果读取到的是操作数,直接加入到有序集合。如果读取到的是如“+”,“*”,“(”操作符,那我们从栈中弹出栈元素直到发现优先级更低的元素为止。一个例外是处理“)”的时候,否则我们绝不从栈中移走“(”。
如 a + b * c,依次读取操作数和操作符。读取“a”,加入到有序集合,读取“+”,此时栈为空,直接入栈,读取“b”,加入有序集合。此时:
接着 * 被读入,此时待查元素优先级高于栈顶元素,执行操作符入栈,接着读入c,加入到有序集合。此时:
表达式读取完毕,而有序队列中显然只有操作数没有操作符,接下来则按照优先级的顺序依次将栈内操作符写入到有序集合(这也是为什么使用栈存储操作符的原因,优先级高的先弹出也就先进行运算)
最后有序集合中按顺序即为中缀表达式的后缀记法。
同级操作符之间的栈内存储是什么样的呢?a + b + c 的后缀是 a b + c + 还是 a b c + + 呢?
那我们先来研究一下后缀表达式是如何进行计算的。
后缀表达式运算
还是以 a + b * c 举例。该表达式的后缀记法:a b c * +。
运算规则:
依次读取表达式的每个元素推入栈中,如果读取到操作符,则对栈顶的2个元素进行操作运算,将运算结果推入栈中,直到最后一个元素读取完毕。不出意外的话,此时栈有且只有一个元素,该元素即为表达式的最后结果。
故 a b c * + 的运算如下:
① 读取 a b c,a b c 依次入栈,
② 读取 *, c 出栈,b出栈做乘法运算,得到 A
③ 将 b * c 的结果A推入栈中,栈内元素 a A
④ 读取 +,A出栈,a出栈做加法运算,得到 B
⑤ 将 a + A 的结果 B 推入栈中,栈内元素 B
读取完毕,栈顶元素 B 即为最后结果。
为了方便理解,这里我们将 a + b + c替换为 a + b - c,研究其后缀是 a b + c - 或 a b - c + 或 a b c - + 或 a b c + -
- 分析 a b + c -
① a b 入栈
② +: b a 出栈,计算 a + b => A,栈内元素 A
③ c入栈,栈内元素 A c
④ -: c A出栈,计算 A - c => B,栈内元素 B
读取完毕,显然这是我们所期望的先进行 a + b 的运算和,再计算 a + b - c的运算结果。 - 对 a b c + -进行简单分析
① a b c 入栈
② +: b c 出栈,计算 b + c => A,栈内 a A
③ -: A a 出栈,计算 a - A => B,栈内 B
读取完毕,栈内元素 B 为计算最后结果。不过这样一分析,我们发现这运算结果是 a - (b+c)??显然是不正确的。
可以以同样的方式推一下 a b - c +计算的是 a - b + c,a b c - + 计算的是 a + (b-c)。虽然说 a b c - + 计算结果和预期结果一样,但我们更希望 a+b-c 先做加法,而非减法。
由此总结一下,中缀表达式到后缀表达式,操作符的进栈规则:
① 待查操作符优先级高于栈顶元素:操作符入栈
② 待查操作符优先级不高于栈顶元素:从栈中弹出栈元素直到发现优先级更低的元素为止
结语
如果本文把你绕的糊涂了,嗯…我也没有办法,文字水平有限。当然本文都是以简单的例子来进行说明的,更复杂的还是需要自己理解,包括括号部分…
贴一下我自己写的代码,自己测试是没有问题的。如果代码部分各位有不错的优化方法或实现方式,欢迎评论分享。
/**
*
* 中缀表达式 --> 后缀表达式
* 将中缀表达式转为后缀表达式
*
* 测试用例:
* 中缀:4.99 * 1.06 + 5.99 + 6.99 * 1.06
* 后缀:4.99 1.06 * 5.99 + 6.99 1.06 * +
*/
private static String[] infixToSuffixArr(String stackStr) {
String[] items = stackStr.trim().split(" "); //重点不在这里,表达式的元素分割没有特别进行说明哈
List<String> itemList = new ArrayList<>();
Stack<Character> optStack = new Stack<>();
for(String item : items) {
if(isNumString(item)) {
itemList.add(item);
continue;
}
//待查元素优先级低于栈顶元素的运算符:栈顶元素加入队列,待查元素入栈,否则待查元素加入队列
if(optStack.size() > 0) {
char topOpt = optStack.pop();
switch (item.charAt(0)) {
case '+':
case '-':
while(topOpt == '*' || topOpt == '/') {
itemList.add(String.valueOf(topOpt));
if(optStack.size() > 0) {
topOpt = optStack.pop();
} else {
itemList.remove(itemList.size()-1);
break;
}
}
if(topOpt == '(') {
optStack.push(topOpt);
optStack.push(item.charAt(0));
} else {
optStack.push(item.charAt(0));
itemList.add(String.valueOf(topOpt));
}
break;
case '*':
case '/':
case '(':
optStack.push(topOpt);
optStack.push(item.charAt(0));
break;
case ')':
//出栈直到第一个开放符号
while(topOpt != '(') {
itemList.add(String.valueOf(topOpt));
if(optStack.size() > 0) {
topOpt = optStack.pop();
} else {
throw new IllegalArgumentException();
}
}
break;
}
} else {
optStack.push(item.charAt(0));
}
}//for
while(optStack.size() > 0) {
itemList.add(String.valueOf(optStack.pop()));
}
optStack = null;
items = new String[itemList.size()];
items = itemList.toArray(items);
return items;
}
测试结果:
顺便记录一下后缀表达式的计算
/**
* 栈的后缀表达式运算
*/
private static double suffixCount(String[] items) {
if(null == items || items.length <= 0) {
return 0;
}
for (String item : items) {
if(isNumString(item)) {
stack.push(Double.parseDouble(item));
} else {
//出栈时,栈内元素至少为 2 个
if(null == stack || stack.size() < 2) {
throw new IllegalArgumentException("使用双目运算符运算前,栈内元素至少为 2 个");
}
double behind = stack.pop();
double prev = stack.pop();
switch (item) {
case "+":
//do add
stack.push(prev + behind);
break;
case "-":
stack.push(prev - behind);
break;
case "*":
stack.push(prev * behind);
break;
case "/":
try {
stack.push(prev / behind);
} catch (ArithmeticException e) {
System.out.println("分母不能为 0");
}
break;
}
}//if
}
if(stack != null) {
if(stack.size() == 1) {
return stack.pop();
}
System.out.println("错误的表达式");
}
throw new EmptyStackException();
}//stack