1. 前缀表达式求值
题目:
算术表达式有前缀表示法、中缀表示法和后缀表示法等形式。前缀表达式指二元运算符位于两个运算数之前,例如2+3*(7-4)+8/4的前缀表达式是:+ + 2 * 3 - 7 4 / 8 4。请设计程序计算前缀表达式的结果值。
输入格式:
输入在一行内给出不超过30个字符的前缀表达式,只包含+、-、*、/以及运算数,不同对象(运算数、运算符号)之间以空格分隔。
输出格式:
输出前缀表达式的运算结果,保留小数点后1位,或错误信息ERROR。
输入样例:
+ + 2 * 3 - 7 4 / 8 4
输出样例:
13.0
分析与解
利用栈来计算前缀表达式将非常简单,我们只需要一个操作数栈就可以了,不需要操作符栈。具体的操作是,从左到右遍历表达式,读到数字就入栈,读到操作符就出栈两次,将取出来的这两个数进行对应操作。
需要注意的是,数字可能不止一位,可能有小数点,可能是负数,可能只有1个数字且前有+号……数字的读取也是本例的重点。
下面请看Java题解:
import sun.font.FontRunIterator;
import java.util.Scanner;
import java.util.Stack;
import java.util.zip.CheckedOutputStream;
public class EvaluatePrefixExpressions {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//pe:prefix expression, 前缀表达式
String pe = sc.nextLine();
evaluate(pe);
}
public static double evaluate(String pe){
//操作数栈
Stack<Double> numSt = new Stack<>();
int len = pe.length();
//从右到左遍历表达式
for(int i = len-1; i>=0; i--){
if(Character.isDigit(pe.charAt(i))){
int mul = 10;
double num = pe.charAt(i) - '0';
for(i--; i>=0; i--){
if(Character.isDigit(pe.charAt(i))){
num += (pe.charAt(i) - '0')*mul;
mul *= 10;
}else if(pe.charAt(i) == '.'){
num /= mul;
mul = 1;
}else if(pe.charAt(i) == '-'){
num = -num;
}else{
break;
}
}
numSt.push(num);
}else if(pe.charAt(i) != ' ' && numSt.size() >= 2){
double a = numSt.peek();
numSt.pop();
double b = numSt.peek();
numSt.pop();
double sum = 0;
switch (pe.charAt(i)){
case '+': sum = a + b;break;
case '-': sum = a - b;break;
case '*': sum = a * b;break;
case '/':
if(b == 0){
System.out.println("ERROR");
return -1;//-1代表出错了
}
sum = a / b;break;
}
numSt.push(sum);
}
}
System.out.println(String.format("%.1f", numSt.peek()));
return numSt.peek();
}
}
2. LeetCode 150.逆波兰表达式求值
题目:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
逆波兰表达式的优点:
去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:
该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
提示:
1 <= tokens.length <= 104
tokens[i] 要么是一个算符("+"、"-"、"*" 或 "/"),要么是一个在范围 [-200, 200] 内的整数
分析与解:
本题和第一个例子不同的是,数字用字符串来存储、不考虑小数、不考虑只有1个数字且前有+号的情况、不考虑除数是0的情况……当然,负数的情况还是要讨论的。基本上本题的读取数字部分没有什么“坑”。
注意本例与第一个例子的另一个不同之处在于,减法和除法的顺序是相反的。
解题思路和前缀表达式一样,遇到数字就入栈,遇到操作符就取出两个数进行对应操作。当然前缀表达式是从右往左遍历,而后缀表达式是从左往右遍历。
请看Java题解:
class Solution {
public int evalRPN(String[] tokens) {
int n = tokens.length;
Stack<Integer> numSt = new Stack<>();
for(int i=0; i<n; i++){
if(!tokens[i].equals("+") && !tokens[i].equals("-")
&& !tokens[i].equals("*") && !tokens[i].equals("/")){
int len = tokens[i].length();
int num = 0;
int mul = 1;
for(int j=len-1; j>=0; j--){
if(tokens[i].charAt(j) == '-'){
num = -num;
break;
}
num += (tokens[i].charAt(j) - '0')*mul;
mul *= 10;
}
numSt.push(num);
}else{
int sum = 0;
int a = numSt.pop();
int b = numSt.pop();
switch(tokens[i]){
case "+": sum = a + b;break;
case "-": sum = b - a;break;
case "*": sum = a * b;break;
case "/": sum = b / a;break;
}
numSt.push(sum);
}
}
return numSt.peek();
}
}
PS:本题当然也可像“1.前缀表达式求值”那样,魔改成“输入字符串表达式”和“添加浮点数功能”,但是需要借助String.valueOf()和Double.parseDouble()来进行数据的转换。
3. 中缀表达式求值
主要实现这几个功能:
1.把String类型的中缀表达式转换为List类型。
2.把中缀表达式转换为后缀表达式,仍保留List类型。
3.计算后缀表达式,照搬“2.逆波兰表达式求值”。
代码参考这篇文章:【LeetCode(Java) - 772】基本计算器 III,但修改了将表达式转换为List类型的方法,添加了转换浮点数的功能。当然,LeetCode772只要求计算整数,所以按照链接里这篇文章的代码就能通过LeetCode772了。
下面是代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
public class EvaluateInfixExpressions {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
System.out.println(calculate(s));
}
static double calculate(String s){
//去除空格
String expression = s.replace(" ", "");
//字符串表达式集合化,设计括号
List<String> infix = expressionToList(expression);
//中缀转后缀
List<String> suffix = infixToSuffix(infix);
//存储中间结果
Stack<String> stk = new Stack<>();
//逆波兰计算
int sz = suffix.size();
for(int i=0; i<sz; i++){
String tmp = suffix.get(i);
if(isOper(tmp)){
String num1 = stk.pop();
String num2 = stk.pop();
String result = cal(num1, tmp, num2);
stk.push(result);
}else {
stk.push(tmp);
}
}
return Double.parseDouble(stk.pop());
}
static List<String> expressionToList(String expression){
List<String> list = new ArrayList<>();
int len = expression.length();
StringBuilder sb = new StringBuilder();
for(int i=0; i<len; i++){
if(isNum(expression, i)){
sb.append(expression.charAt(i));
if(i==len-1 || !isNum(expression,i+1)){
String num = sb.toString();
list.add(num);
sb = new StringBuilder();
}
}else{
//+""的原因是要把char转为String
list.add(expression.charAt(i) + "");
}
};
return list;
}
static List<String> infixToSuffix(List<String> infix){
int sz = infix.size();
List<String> suffix = new ArrayList<>();
Stack<String> operStk = new Stack<>();
//Stack<String> stk2 = new Stack<>();
for(int i=0; i<sz; i++){
//element in the list
String e = infix.get(i);
if(isOper(e)){
//empty判断要在前面
//虽然"("优先级最小,但是入栈的时候不需要碾压任何操作符,直接入栈,只有别的操作符跟它比较的时候"("的优先级才最小
if(operStk.empty() || e.equals("(")){
operStk.push(e);
}else{
if(e.equals(")")){
//对empty的判断必须在前面,下同,否则会出现虽然是空栈但却有peek操作的错误
while( !operStk.empty() && !operStk.peek().equals("(") ){
suffix.add(operStk.pop());
}
if(!operStk.empty())operStk.pop();
}else{
//栈顶操作符不小于当前操作符时,出栈
while( !operStk.empty() && priority(e) <= priority(operStk.peek()) && !operStk.empty() ){
suffix.add(operStk.pop());
}
operStk.push(e);
}
}
}else{
suffix.add(e);
}
}
while(!operStk.empty()){
suffix.add(operStk.pop());
}
return suffix;
}
static String cal(String num1, String tmp, String num2){
double a = Double.parseDouble(num1);
double b = Double.parseDouble(num2);
double res = -1;
switch (tmp){
case "+": res = a + b;break;
//注意顺序
case "-": res = b - a;break;
case "*": res = a * b;break;
//注意顺序
case "/": res = b / a;break;
}
return String.valueOf(res);
}
static boolean isOper(String str){
return str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/") ||
str.equals("(") || str.equals(")");
}
static boolean isNum(String str, int i){
char c1 = str.charAt(i);
char c2 = '\0';
if(i>0) c2 = str.charAt(i-1);
return Character.isDigit(c1) || c1 == '.' ||
(i==0 || c2=='(' || c2=='+' || c2=='-' || c2=='*' || c2=='/')&&(c1=='+' || c1=='-');
}
static int priority(String str){
if(str.equals("+") || str.equals("-")){
return 0;
}else if(str.equals("*") || str.equals("/")){
return 1;
}else{
return -1;
}
}
}
4.总结
本文介绍了前缀表达式、后缀表达式、中缀表达式的求值方法。其中的前缀表达式和后缀表达式,借助栈进行求值,非常容易实现。所以在中缀表达式求值时,我们可以先将其转换为后缀表达式,再进行计算就容易了。如果直接计算,处理中缀表达式的括号是比较复杂的,需要用到递归。