[ 数据结构与算法 ] 逆波兰计算器

逆波兰计算器

应用场景

  1. 输入一个逆波兰表达式(后缀表达式),使用栈(Stack), 计算其结果
  2. 支持小括号和多位数整数,因为这里我们主要讲的是数据结构,因此计算器进行简化,只支持 对整数的计算。

计算后缀表达式

案例: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值

关键点:判断扫描到的字符串:1.数字直接入栈;2.若是运算符,出栈两个数字并计算,计算结果放回栈中

步骤如下:
1.从左至右扫描,将 3 和 4 压入堆栈;
2.遇到+运算符,因此弹出 4 和 3(4 为栈顶元素,3 为次顶元素),计算出 3+4 的值,得 7,再将 7 入栈;
3.将 5 入栈;
4.接下来是×运算符,因此弹出 5 和 7,计算出 7×5=35,将 35 入栈;
5.将 6 入栈;
6.最后是-运算符,计算出 35-6 的值,即 29,由此得出最终结果

代码实现:

//逆波兰计算器:使用Stack计算后缀表达式
public class ReversePolandNotation {
    public static void main(String[] args) {
        //中缀:(30+4)*5-6
        //后缀:30 4 + 5 * 6 -
        String expression = "30 4 + 5 * 6 -";

        //该栈用于存放数字和临时计算结果
        Stack<Integer> stack = new Stack<>();
        int num1;
        int num2;
        String opera;
        String[] s = expression.split(" ");
        for (String string : s) {//遍历表达式字符串
            //数组元素为运算符:出栈两个数字并运算,运算结果入栈
            if (isOpera(string)) {
                num1 = stack.pop();
                num2 = stack.pop();
                opera = string;
                stack.push(calcu(num1, num2, opera));
            //数组元素为数字,直接入栈
            } else {
                stack.push(Integer.parseInt(string));
            }
        }
        System.out.println(stack.pop());//表达式计算结果
    }

    //判断数组元素是否运算符
    public static boolean isOpera(String s) {
        return s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/");
    }

    //计算三个元素
    public static int calcu(int num1,int num2,String opera) {
        int result = 0;
        switch (opera) {
            case "+":
                result = num2 + num1;
                break;
            case "-":
                result = num2 - num1;
                break;
            case "*":
                result = num2 * num1;
                break;
            case "/":
                result = num2 / num1;
                break;
        }
        return result;
    }
}

计算中缀表达式

通过计算后缀表达式代码实现过程可以看到:

  1. 计算过程简单
  2. 可以解决较长且较复杂的表达式
  3. 完美解决去括号的问题
  4. 但正常人却很难写出来
  5. 因此处理中缀表达式可以先将其转换为后缀表达式,再使用逆波兰计算器完成计算

关键点提示:

  1. 括号问题:

    当扫描到 ’ ( ’ ,直接入栈,而扫描到 ’ ) ',将s1(符号栈)栈顶运算符反复取出放入s2(储存中间结果栈),直到对应的 ’ ( ’ 出栈.此过程就是去括号

  2. 优先级问题:

    扫描到的运算符优先级小于栈顶,取出s1栈顶入s2栈并重新将索引–指回该字符

    否则即为优先级大于或等于栈顶:直接入s1栈

  3. 多位数问题:

    仍使用字符串追加方式

  4. 收尾:

    s1中剩余的运算符顺序放入s2,最终s2中的所有元素就是完整的后缀表达式

  5. 计算:

    将s2中的元素出栈并存入临时集合,反向遍历元素同时,使用逆波兰计算器完成计算

具体步骤如下:

  1. 初始化两个栈:运算符栈 s1 和储存中间结果的栈 s2;
  2. 从左至右扫描中缀表达式;
  3. 遇到操作数时,将其压 s2;
  4. 遇到运算符时,比较其与 s1 栈顶运算符的优先级:
    1.如果 s1 为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    2.否则,若优先级比栈顶运算符的高,也将运算符压入 s1;
    3.否则,将 s1 栈顶的运算符弹出并压入到 s2 中,再次转到(4-1)与 s1 中新的栈顶运算符相比较;
  5. 遇到括号时:
    (1) 如果是左括号“(”,则直接压入 s1
    (2) 如果是右括号“)”,则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃
  6. 重复步骤 2 至 5,直到表达式的最右边
  7. 将 s1 中剩余的运算符依次弹出并压入 s2
  8. 依次弹出 s2 中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

案例:

将中缀表达式“1+((2+3)×4)-5”转换为后缀表达式的过程如下
因此结果为 :“1 2 3 + 4 × + 5 –”

debug可观察到如下过程变化:

image-20220311111701433

代码实现:

//计算中缀表达式(带括号)将一个中缀表达式转成后缀表达式并计算
public class RPNCalculator {
    public static void main(String[] args) {

        Stack<Character> s1 = new Stack<>();
        Stack<String> s2 = new Stack<>();

        String expression = "";
        expression = "1+((2+3)*4)-5";//1 2 3 + 4 * + 5 -
        expression = "3+(4*(1+1)+1)*2";//出栈+*2+1*+1143 后缀:3 4 1 1 + * 1 + 2 * +
        int index = 0;
        String num = "";
        ArrayList<Character> characters;
        while (true) {
            if (index>=expression.length()) {//扫描完毕退出循环
                break;
            }
            char ch = expression.charAt(index);
            index ++;
            if (isOpera(ch)) {//字符为运算符
                //以下条件满足任一直接入s1栈
                //1.栈为空
                //2.栈顶为'('
                //3.该字符为'('
                if (s1.empty() || peek(s1) == '('||ch=='(') {
                    s1.push(ch);
                //判断该字符为')'执行:
                //循环取出s1栈顶入s2直到取出'('为止
                } else if (ch == ')') {
                    while (true) {
                        if (peek(s1) == '(') {
                            s1.pop();
                            break;
                        }
                        s2.push("" + s1.pop());
                    }
                }else {//剩余情况:

                    //该字符优先级小于栈顶:取出s1栈顶入s2栈并重新将索引--指回该字符
                    if (priority(ch) < priority(s1.peek())) {
                        s2.push("" + s1.pop());
                        index--;
                        //否则即为优先级大于或等于栈顶:直接入s1栈
                    } else {
                        s1.push(ch);
                    }

                }
            } else {//字符为数字
                //将该字符追加到字符串末尾
                num += ch;
                //判断是否为最后一个字符,或下一个字符为运算符:直接入s2栈,然后数字字符串置空
                if (index==expression.length()||isOpera(expression.charAt(index))) {
                    s2.push(num);
                    num = "";
                }
            }
        }
        //将s1剩余运算符出栈同时入s2栈
        while (true) {
            if (s1.empty()) {
                break;
            }
            s2.push("" + s1.pop());
        }

        //到此行,中缀转后缀的完整的后缀表达式入s2栈完毕
        /*while (true) {
            if (s2.empty()) {
                break;
            }
            System.out.print(s2.pop());
        }*/

        //s2元素出栈
        ArrayList<String> elements = new ArrayList<>();
        while (true) {
            if (s2.empty()) {
                break;
            }

            elements.add(s2.pop());
        }
        //数字栈,用于存放临时计算结果
        Stack<Integer> stack = new Stack<>();
        int num1;
        int num2;
        String opera;
        //倒序遍历集合中的元素,使用逆波兰计算器,借助一个数字栈计算,将计算结果存入该栈
        for (int i = elements.size()-1; i >=0 ; i--) {
            //数组元素为运算符:出栈两个,运算结果入栈
            if (isOpera(elements.get(i))) {
                num1 = stack.pop();
                num2 = stack.pop();
                opera = elements.get(i);
                stack.push(calcu(num1, num2, opera));
                //数组元素为数字,直接入栈
            } else {
                stack.push(Integer.parseInt(elements.get(i)));
            }
        }
        System.out.println(stack.pop());//表达式计算结果
    }

    //判断字符是否运算符
    public static boolean isOpera(char ch) {
        return ch == '+' || ch == '-' || ch == '*' || ch == '/'||ch=='('||ch==')';
    }

    //判断数组元素(字符串)是否运算符
    public static boolean isOpera(String s) {
        return s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/");
    }

    //计算三个元素
    public static int calcu(int num1,int num2,String opera) {
        int result = 0;
        switch (opera) {
            case "+":
                result = num2 + num1;
                break;
            case "-":
                result = num2 - num1;
                break;
            case "*":
                result = num2 * num1;
                break;
            case "/":
                result = num2 / num1;
                break;
        }
        return result;
    }

    //返回操作符的优先级
    public static int priority(char opera) {
        if (opera == '*' || opera == '/') {
            return 1;
        }else if (opera == '+' || opera == '-') {
            return 0;
        }
        return -1;
    }

    //返回栈顶元素
    public static Character peek(Stack<Character> stack) {
        Character result =stack.pop();

                stack.push(result);
            return result;

    }
}

代码优化

思路:

  1. 将中缀表达式存入集合
  2. 中缀表达式集合解析并转换成后缀表达式存入集合
  3. 逆波兰计算器计算后缀表达式

步骤:

  1. 编写转换方法,方法中将中缀表达式字符串中元素 区分多位数和运算符两种元素按顺序添加到集合

  2. 编写解析方法将中缀转后缀:

    这里只使用一个栈和一个集合搭配,因为如果使用两个栈,存放中间结果的栈最终出栈元素还需 反转 才得到 想要的后缀表达式,使用集合存放简化了这一步骤

    1)遍历传入的集合,判断是否多位数,直接放入集合

    2)如果是左括号直接入栈

    3)如果是右括号,反复观察栈顶是否是左括号,反复 出栈并放入集合直到遇左括号

    4)剩余情况就是运算符了:按优先级操作

    5)收尾:将剩余栈中元素存放在集合

  3. 逆波兰计算器计算后缀表达式

//优化代码并封装方法实现中缀转后缀再计算
public class HSPCalculator {
    public static void main(String[] args) {
        String infixExpression = "1+((2+3)*4)-5";//中缀表达式
        List<String> infixExpressionList = toInfixExpressionList(infixExpression);//中缀拆开放入集合
        List<String> suffixExpressionList = parseSuffixExpressionList(infixExpressionList);//后缀表达式1 2 3 + 4 * + 5 -
        System.out.println(calc(suffixExpressionList));

    }

    //逆波兰计算器
    public static int calc(List<String> ls) {
        Stack<Integer> stack = new Stack<>();
        int num1;
        int num2;
        String opera;

        for (String string : ls) {//遍历表达式字符串
            //数组元素为运算符:出栈两个数字并运算,运算结果入栈
            if (isOpera(string)) {
                num1 = stack.pop();
                num2 = stack.pop();
                opera = string;
                stack.push(calcu(num1, num2, opera));
                //数组元素为数字,直接入栈
            } else {
                stack.push(Integer.parseInt(string));
            }
        }
        return stack.pop();//表达式计算结果
    }

    //将中缀List转成后缀List
    public static List<String> parseSuffixExpressionList(List<String> ls) {
        Stack<String> s1 = new Stack<>();//符号栈
        ArrayList<String> s2 = new ArrayList<>();//存放中间结果的集合

        for (String item : ls) {
            if (item.matches("\\d+")) {
                s2.add(item);
            } else if (item.equals("(")) {
                s1.push(item);
            } else if (item.equals(")")) {
                while (!s1.peek().equals("(")) {
                    s2.add(s1.pop());
                }
                s1.pop();
            } else {
                while (s1.size() != 0 && priority(s1.peek()) >= priority(item)) {

                    s2.add(s1.pop());
                }
                s1.push(item);
            }
        }
        while (s1.size() != 0) {
            s2.add(s1.pop());
        }

        return s2;
    }


    //方法:将中缀表达式转成List:多位数和运算符分别以字符串类型存入集合
    public static List<String> toInfixExpressionList(String s) {
        ArrayList<String> ls = new ArrayList<>();
        int index = 0;

        String num;
        char ch;
        do {

            if ((ch = s.charAt(index)) < 48 || (ch = s.charAt(index)) > 57) {
                ls.add("" + ch);
                index++;
            } else {
                num = "";
                while (index < s.length() && ((ch = s.charAt(index)) >= 48 && (ch = s.charAt(index)) <= 57)) {
                    num += ch;
                    index++;
                }
                ls.add(num);
            }

        } while (index < s.length());
        return ls;
    }


    //返回操作符的优先级
    public static int priority(String opera) {
        if (opera .equals("*") || opera .equals("/") ) {
            return 2;
        }else if (opera .equals("+")  || opera .equals("-")) {
            return 1;
        }
        return 0;
    }

    //判断数组元素是否运算符
    public static boolean isOpera(String s) {
        return s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/");
    }

    //计算三个元素
    public static int calcu(int num1,int num2,String opera) {
        int result = 0;
        switch (opera) {
            case "+":
                result = num2 + num1;
                break;
            case "-":
                result = num2 - num1;
                break;
            case "*":
                result = num2 * num1;
                break;
            case "/":
                result = num2 / num1;
                break;
        }
        return result;
    }
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值