【数据结构】栈与队列01:表达式的求值与转换

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.总结

本文介绍了前缀表达式、后缀表达式、中缀表达式的求值方法。其中的前缀表达式和后缀表达式,借助栈进行求值,非常容易实现。所以在中缀表达式求值时,我们可以先将其转换为后缀表达式,再进行计算就容易了。如果直接计算,处理中缀表达式的括号是比较复杂的,需要用到递归。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值