java 实现后缀表达式

一、概述

后缀表达式(也称为逆波兰表达式)是一种数学表达式的表示方法,其中操作符位于操作数的后面。这种表示法消除了括号,并且在计算机科学和计算中非常有用,因为它更容易计算和解析。

与中缀表达式(通常我们使用的数学表达式,例如"a * (b + c)")不同,后缀表达式的运算符放在操作数之后,例如:"a b c + *"。后缀表达式的计算方法是从左到右遍历表达式,遇到操作数时将其压入栈,遇到操作符时从栈中弹出所需数量的操作数进行计算,然后将结果重新压入栈。这个过程一直持续到整个表达式处理完毕,最终栈中只剩下一个结果,即表达式的计算结果。

后缀表达式具有以下优点:

  1. 不需要括号,因此消除了歧义。
  2. 更容易计算,因为遵循一定的计算顺序。
  3. 适用于计算机的堆栈操作,因此在编译器和计算器中经常使用。

转换中缀表达式为后缀表达式需要使用算法,通常是栈数据结构。

二、后缀表达式的运算顺序

后缀表达式的运算顺序是从左到右遍历表达式,遇到操作数时将其压入栈,遇到操作符时从栈中弹出所需数量的操作数进行计算,然后将计算结果重新压入栈。这个过程一直持续到整个表达式处理完毕,最终栈中只剩下一个结果,即表达式的计算结果。

后缀表达式的运算顺序是非常直观的,它遵循从左到右的顺序。当计算后缀表达式时,按照以下规则:

  1. 从左到右扫描后缀表达式中的每个元素(操作数或操作符)。
  2. 如果遇到操作数,将其推入栈。
  3. 如果遇到操作符,从栈中弹出所需数量的操作数进行计算,然后将计算结果推回栈中。
  4. 重复这个过程,直到遍历完整个后缀表达式。

三、常规表达式转化为后缀表达式

  • 创建两个栈,一个用于操作符(操作符栈),另一个用于输出后缀表达式(输出栈)。
  • 从左到右遍历中缀表达式的每个元素。
  • 如果是操作数,将其添加到输出栈。
  • 如果是操作符:
  • 如果操作符栈为空,直接将该操作符推入操作符栈。
    否则,比较该操作符与操作符栈栈顶的操作符的优先级。如果当前操作符的优先级较高,将其推入操作符栈。
    如果当前操作符的优先级较低或相等,从操作符栈中弹出并添加到输出栈,然后重复比较直到可以推入操作符栈。
    如果遇到左括号"(",直接推入操作符栈。
    如果遇到右括号")",将操作符栈中的操作符弹出并添加到输出栈,直到遇到匹配的左括号"("。
    最后,将操作符栈中的剩余操作符全部弹出并添加到输出栈。
    完成遍历后,输出栈中的内容就是中缀表达式转化为后缀表达式的结果。

四、代码实现

java 复制代码
/**
     * 定义操作符的优先级
     */
    private  Map<String, Integer> opList =
            Map.of("(",3,")",3,"*",2,"/",2,"+",1,"-",1);

    public List<String> getPostExp(List<String> source) {

        // 数字栈
        Stack<String> dataStack = new Stack<>();
        // 操作数栈
        Stack<String> opStack = new Stack<>();
        // 操作数集合
        for (int i = 0; i < source.size(); i++) {
            String d = source.get(i).trim();
            // 操作符的操作
            if (opList.containsKey(d)) {
                operHandler(d,opStack,dataStack);
            } else {
                // 操作数直接入栈
                dataStack.push(d);
            }
        }
        // 操作数栈中的数据,到压入到栈中
        while (!opStack.isEmpty()) {
            dataStack.push(opStack.pop());
        }
        List<String> result = new ArrayList<>();
        while (!dataStack.isEmpty()) {
            String pop = dataStack.pop();
            result.add(pop);
        }
        // 对数组进行翻转
        return CollUtil.reverse(result);
    }

    /**
     * 对操作数栈的操作
     * @param d,当前操作符
     * @param opStack 操作数栈
     */
    private void operHandler(String d, Stack<String> opStack,Stack<String> dataStack) {
        // 操作数栈为空
        if (opStack.isEmpty()) {
            opStack.push(d);
            return;
        }
        // 如果遇到左括号"(",直接推入操作符栈。
        if (d.equals("(")) {
            opStack.push(d);
            return;
        }
        // 如果遇到右括号")",将操作符栈中的操作符弹出并添加到输出栈,直到遇到匹配的左括号"("。
        if (d.equals(")")) {
            while (!opStack.isEmpty()) {
                String pop = opStack.pop();
                // 不是左括号
                if (!pop.equals("(")) {
                    dataStack.push(pop);
                } else {
                    return;
                }
            }
        }
        // 操作数栈不为空
        while (!opStack.isEmpty()) {
            // 获取栈顶元素和优先级
            String peek = opStack.peek();
            Integer v = opList.get(peek);
            // 获取当前元素优先级
            Integer c = opList.get(d);
            // 如果当前操作符的优先级较低或相等,且不为(),从操作符栈中弹出并添加到输出栈,然后重复比较直到可以推入操作符栈
            if (c < v && v != 3) {
                // 出栈
                opStack.pop();
                // 压入结果集栈
                dataStack.push(peek);
            } else {
                // 操作符与操作符栈栈顶的操作符的优先级。如果当前操作符的优先级较高,将其推入操作符栈。
                opStack.push(d);
                break;
            }
        }
    }

测试代码如下:

java 复制代码
PostfixExpre postfixExpre = new PostfixExpre();

List<String> postExp = postfixExpre.getPostExp(
        Arrays.asList("9", "+", "(" , "3", "-", "1", ")", "*", "3", "+", "10", "/", "2"));

System.out.println(postExp);

输出如下:

csharp 复制代码
[9, 3, 1, -, 3, *, 10, 2, /, +, +]

五、求后缀表示值

使用栈来实现

java 复制代码
    /****
     * 计算后缀表达式的值
     * @param source
     * @return
     */
    public double calcPostfixExpe(List<String> source) {

        Stack<String> data = new Stack<>();
        for (int i = 0; i < source.size(); i++) {
            String s = source.get(i);
            // 如果是操作数
            if (opList.containsKey(s)) {
                String d2 = data.pop();
                String d1 = data.pop();
                Double i1 = Double.valueOf(d1);
                Double i2 = Double.valueOf(d2);
                Double result = null;
                switch (s) {
                    case "+":
                        result = i1 + i2;break;
                    case "-":
                        result = i1 - i2;break;
                    case "*":
                        result = i1 * i2;break;
                    case "/":
                        result = i1 / i2;break;
                }
                data.push(String.valueOf(result));
            } else {
                // 如果是操作数,进栈操作
                data.push(s);
            }
        }
        // 获取结果
        String pop = data.pop();
        return Double.valueOf(pop);
    }

测试

csharp 复制代码
PostfixExpre postfixExpre = new PostfixExpre();

List<String> postExp = postfixExpre.getPostExp(
        Arrays.asList("9", "+", "(" , "3", "-", "1", ")", "*", "3", "+", "10", "/", "2"));

System.out.println(postExp);

double v = postfixExpre.calcPostfixExpe(postExp);

System.out.println(v);

结果如下:

csharp 复制代码
[9, 3, 1, -, 3, *, 10, 2, /, +, +]
20.0
相关推荐
monkey_meng39 分钟前
【Rust中的迭代器】
开发语言·后端·rust
余衫马42 分钟前
Rust-Trait 特征编程
开发语言·后端·rust
monkey_meng1 小时前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
paopaokaka_luck5 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
码农小旋风7 小时前
详解K8S--声明式API
后端
Peter_chq7 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml47 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~7 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616887 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
睡觉谁叫~~~8 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust