####中缀表达式
- 我们平时所见的表达式都是中缀表达式,如( 3 + 4 )x 5 - 6 、1 + 2 * 4 + 6 / 2
- 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式)
####前缀表达式
- 前缀表达式也叫做波兰表达式
- 前缀表达式的运算符位于操作数之前,如( 3 + 4 )x 5 - 6对应的中缀表达式是 - x + 3 4 5 6
- 前缀表达式计算求值:
从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
例如: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:
3.1 从右至左扫描,将6、5、4、3压入堆栈
3.2 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),栈顶第一个操作数,次顶元素是第二个操作数,因此计算出3+4的值,得7,再将7入栈
3.3 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
3.4 最后是-运算符,计算出35-6的值,即29,由此得出最终结果 - 中缀表达式转换成前缀表达式步骤
(1)初始化两个栈:运算符栈S1和储存中间结果的栈S2;
(2) 从右至左扫描中缀表达式;
(3) 遇到操作数时,将其压入S2;
(4) 遇到运算符时,比较其与S1栈顶运算符的优先级:
(4-1) 如果S1为空,或栈顶运算符为右括号“)”,则直接将此运算符入栈;
(4-2) 否则,若优先级比栈顶运算符的较高或相等,也将运算符压入S1;
(4-3) 否则,将S1栈顶的运算符弹出并压入到S2中,再次转到(4-1)与S1中新的栈顶运算符相比较;
(5) 遇到括号时:
(5-1) 如果是右括号“)”,则直接压入S1;
(5-2) 如果是左括号“(”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止,此时将这一对括号丢弃;
(6) 重复步骤(2)至(5),直到表达式的最左边;
(7) 将S1中剩余的运算符依次弹出并压入S2;
(8) 依次弹出S2中的元素并输出,结果即为中缀表达式对应的前缀表达式。
####后缀表达式
- 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
- 举例说明:
正常表达式 | 后缀表达式 |
---|---|
( 3 + 4 ) × 5 - 6 | 3 4 + 5 × 6 – |
a + b | a b + |
a + ( b - c ) | a b c - + |
a + ( b - c ) * d | a b c – d * + |
a + d * ( b - c ) | a d b c - * + |
a = 1 + 3 | a 1 3 + = |
- 后缀表达式的计算求值:
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
(1)从左至右扫描,将3和4压入堆栈;
(2)遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
(3)将5入栈;
(4)接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
(5)将6入栈,最后是-运算符,计算出35-6的值,即29,由此得出最终结果 - 中缀表达式转换成后缀表达式步骤
(1) 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
(2)从左至右扫描中缀表达式;
(3)遇到操作数时,将其压s2;
(4)遇到运算符时,比较其与s1栈顶运算符的优先级:
(4-1) 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
(4-2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
(4-3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;
(5)遇到括号时:
(5-1)如果是左括号“(”,则直接压入s1
(5-2)如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
(6)重复步骤2至5,直到表达式的最右边
(7)将s1中剩余的运算符依次弹出并压入s2
(8)依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式 - 中缀转后缀表达式举例:将“1+((2+3)×4)-5”转为后缀表达式
扫描到的元素 | s2(栈底->栈顶) | s1 (栈底->栈顶) | 说明 |
---|---|---|---|
1 | 1 | 空 | 数字,直接入栈 |
+ | 1 | + | s1为空,运算符直接入栈 |
( | 1 | + ( | 左括号,直接入栈 |
( | 1 | + ( ( | 同上 |
2 | 1 2 | + ( ( | 数字 |
+ | 1 2 | + ( ( + | s1栈顶为左括号,运算符直接入栈 |
3 | 1 2 3 | + ( ( + | 数字 |
) | 1 2 3 + | + ( | 右括号,弹出运算符直至遇到左括号 |
x | 1 2 3 + | + ( × | s1栈顶为左括号,运算符直接入栈 |
4 | 1 2 3 + 4 | + ( × | 数字 |
) | 1 2 3 + 4 x | + | 右括号,弹出运算符直至遇到左括号 |
- | 1 2 3 + 4 x + | - | -与+优先级相同,因此弹出+,再压入- |
5 | 1 2 3 + 4 × + 5 | - | 数字 |
到达最有端 | 1 2 3 + 4 × + 5 - | 空 | s1中剩余的运算符 |
代码实现
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Stack;
/**
* 后缀(逆波兰)表达式计算器
* 支持运算符号: + - * / ( )
* 支持整数和浮点数
* @author 陈治远
* @create 2019-07-29 18:25
*/
public class SuffixCalculator {
public static void main(String[] args) {
System.out.println("直接计算(-23.6 + 10) * (-10 / 2.0)的结果为:" + (-23.6 + 10) * (-10 / 2.0));
String suffixExperssion = infixToSuffix("(-23.6 + 10) * (-10 / 2.0)");
System.out.println("(-23.6 + 10) * (-10 / 2.0)的后缀表达式为:" + suffixExperssion);
System.out.println("计算结果为:" + calculate(suffixExperssion));
}
/**
* 辅助方法:获得符号的优先级
*/
private static Integer getPriority(String str) {
switch (str) {
case "+":
case "-":
return 1;
case "*":
case "/":
return 2;
default:
throw new RuntimeException("操作符不合法:" + str + ",操作符只应该包括+ - * /");
}
}
/**
* 辅助方法:判断一个字符串是否是数字
*/
private static boolean isNumer(String str) {
try {
Double.parseDouble(str);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 辅助方法:根据操作符oper计算出对应的值
*/
private static double calcuate(double num1, double num2, String oper) {
double res = 0.0;
switch (oper) {
case "+":
res = num1 + num2;
break;
case "-":
res = num1 - num2;
break;
case "*":
res = num1 * num2;
break;
case "/":
res = num1 / num2;
break;
default:
throw new RuntimeException("操作符不正确:" + oper + ",操作符只应该包括+ - * /");
}
return res;
}
/**
* 将中缀表达式转后缀表达式
* @return 返回后缀表达式
*/
public static String infixToSuffix(String infixExperssion) {
Matcher matcher = Pattern.compile("-?\\d+(\\.\\d+)?|\\(|\\)|\\+|\\-|\\*|\\/").matcher(infixExperssion);
Stack<String> s1 = new Stack<>();
Stack<String> s2 = new Stack<>();
while (matcher.find()) {
String str = matcher.group();
if (isNumer(str)) {
// 如果是数字,直接压入s2
s2.push(str);
} else {
// 不是数字
// 如果s1为空,直接压入s1,然后退出当次循环
if (s1.isEmpty()) {
s1.push(str);
continue;
}
if ("(".equals(str)) {
// 如果是左括号,直接压入左括号,然后退出当次循环
s1.push(str);
continue;
} else if (")".equals(str)) {
// 如果是右括号,就弹出s1栈顶符号,压入s2,直到s1栈顶是左括号
while (!"(".equals(s1.peek())) {
s2.push(s1.pop());
}
// 弹出左括号并丢弃,这样就消除了一对括号
s1.pop();
continue;
}
// 如果s1不为空
// 1.如果s1栈顶是左括号,直接进s1栈,不用判断优先级
// 2.如果s1栈顶不是左括号,那么就要比较优先级,如果优先级大于栈顶符号,也直接进s1栈
if ("(".equals(s1.peek()) || getPriority(str) > getPriority(s1.peek())) {
s1.push(str);
continue;
}
// 走到这一步,说明上面所有情况都排除了
// 现在这种情况就是str优先级小于等于s1栈顶符号
// 此时就弹出s1栈顶符号,压入s2,,直到s1栈顶符号的优先级大于str的优先级 或
while (getPriority(str) <= getPriority(s1.peek())) {
String top = s1.pop();
s2.push(top);
// s1每次弹出栈顶后,都判断s1是否为空 或者 s1栈顶为左括号
// 如果是就跳出循环
if (s1.isEmpty() || s1.peek().equals("(")) {
break;
}
}
// 上面while循环只是将优先级>=str的符号弹出并压入s2,没有str的进栈操作
// 循环结束后就说明str具备了进s1栈的资格,这里将str压入s1
s1.push(str);
}
}
// 当中缀表达式扫描完毕,s1中可能还有剩余的符号,应该把它们弹出并压入s2,知道s1为空
while (!s1.isEmpty()) {
String top = s1.pop();
s2.push(top);
}
// 拼接后缀表达式
StringBuilder sb = new StringBuilder();
while (!s2.isEmpty()) {
sb.insert(0, s2.pop() + " ");
}
// 去掉最后一个空格
CharSequence suffixExperssion = sb.subSequence(0, sb.lastIndexOf(" "));
return suffixExperssion.toString();
}
/**
* 计算后缀表达式的值
* @param suffixExperssion 后缀表达式(逆波兰表达式)
* @return
*/
public static double calculate(String suffixExperssion) {
// 后缀表达式中操作数和运算符之间按空格分隔,所以正则表达式匹配所有非空格字符
// 也可以使用String对象的split(regex)得到一个数组,再遍历数组
Matcher matcher = Pattern.compile("\\S+").matcher(suffixExperssion);
// 创建数字栈和符号栈,使用的java.util.Stack
Stack<Double> numStack = new Stack<>();
while (matcher.find()) {
String str = matcher.group();
// 如果子串str是数字,将数字压入numStack
if (isNumer(str)) {
numStack.push(Double.parseDouble(str));
} else {
// 如果子串str是符号,直接在numStack弹两个数然后将计算结果压栈进行计算,然后将结果入栈即可
double num1 = numStack.pop();
double num2 = numStack.pop();
double res = calcuate(num2, num1, str);
// 将计算结果压栈
numStack.push(res);
}
}
// 计算完毕后numStack中的数据就是后缀表达式的值
return numStack.pop();
}
}
控制台打印
直接计算(-23.6 + 10) * (-10 / 2.0)的结果为:68.0
(-23.6 + 10) * (-10 / 2.0)的后缀表达式为:-23.6 10 + -10 2.0 / *
计算结果为:68.0