前缀、中缀、后缀表达式及简易运算实现总结

news2024/11/28 19:02:55

title: 前缀、中缀、后缀表达式及简易运算实现总结
date: 2023-06-30 10:25:50
tags:

  • 表达式
    categories:
  • 开发知识及其他
    cover: https://cover.png
    feature: false

1. 概念

1.1 什么是前缀、中缀、后缀表达式?

  • 前缀表达式:又称波兰式Polish Notation),操作符以前缀形式位于两个运算数前(如:3 + 2 的前缀表达形式就是 + 3 2)
  • 中缀表达式:操作符以中缀形式位于运算数中间(如:3 + 2),是我们日常通用的算术和逻辑公式表示方法
  • 后缀表达式:又称逆波兰式Reverse Polish Notation - RPN),操作符以后缀形式位于两个运算数后(如:3 + 2 的后缀表达形式就是 3 2 +)

中缀表达式往往需要使用括号将操作符和对应的操作数括起来,用于指示运算的次序,如 5 * (2 + 1) 虽然 * 的优先级高于 + ,但括号的存在表示应优先执行括号内的 + 运算。适合于人类的思维结构和运算习惯,但并不适用于计算机

与中缀表达式不同,前缀和后缀表达式都不需要使用括号来标识操作符的优先级,适用于计算机。不过后缀表达式的计算按操作符从左到右出现的顺序依次执行(不考虑运算符之间的优先级),更加符合人类的阅读习惯,因此实际计算机程序中,基本都是用后缀表达式来存储公式的,前缀表达式效果次之。对于中缀表达式,我们则可以先将其转为后缀表达式,再进行求值

1.2 树结构对应

其实前缀表达式、中缀表达式、后缀表达式就是通过树来存储和计算表达式的三种不同方式,分别对应树的先序遍历、中序遍历、后序遍历。如下图,这是一颗二叉树

  • 上面的树,先序遍历就是 *+a−bcd,即对应前缀表达式
  • 中序遍历是 a+b−c∗d,但是这样的表示是有歧义的,这样表示 ab 是一颗子树,cd 是一颗子树,然后相减,所以中缀表达式必须借助括号,才能正确地表达出想要的结果。中缀表达式为:(a+(b−c))∗d,括号表示一个子树的整体
  • 后序遍历是 abc−+d∗,即对应的后缀表达式

2. 表达式求值

2.1 通过树结构存储和求值表达式

实现思路比较简单,如果节点上存的是参数,那么该参数的值,就是该节点的值;如果节点上存的操作符,拿该节点左子树和右子树做对应运算,得到的结果作为该节点的值

代码略

2.2 前缀表达式解析和求值

∗ + a − b c d ∗+a−bcd +abcd

观察前缀表达式的规律可以发现,每当连续出现两个数值时,前面必定会有一个操作符,这是先序遍历的特征决定的(根左右,根即为表达式),因此我们依次取三个元素出来,判断符合连续两个数值条件的进行运算,就可以得到一个操作符节点的数值,如此反复递归,最终就能求出表达式的值

代码略

2.3 后缀表达式解析和求值

a b c − + d ∗ abc−+d∗ abc+d

和前缀表达式类似,其实也就是后序遍历的特征,即只要有运算符出现的地方,前面两个元素一定是操作数(左右根),然后同样取三个元素出来,判断符合条件的进行运算

详细代码见 3

2.4 中缀表达式转后缀表达式

( a + ( b − c ) ) ∗ d (a+(b−c))∗d (a+(bc))d

中缀表达式直接求值比较麻烦,所以我们将其转换为后缀表达式,再求值就方便了。中缀表达式转后缀表达式的难点在于,要考虑括号和运算符优先级,步骤如下,这个转换算法不是凭空产生的,而是根据后缀表达式的特点反推出来的

  1. 创建两个栈,S1 用来存输出元素,S2 用来存运算符。由于表达式中的运算符是有优先级的,所以必须通过栈来暂存起来
  2. 从中缀表达式栈顶开始,向栈尾逐个读取元素
  3. 如果读到操作数,直接加到 S1 栈尾。因为后缀表达式操作数永远是在运算符前面的
  4. 如果读到左括号,则直接压入 S2 栈顶。因为左括号要等到右括号时才能处理
  5. 如果读到运算符,且 S2 栈为空或 S2 栈顶元素为左括号,则直接压入 S2 栈顶。因为这种情况不需要比较运算符优先级
  6. 如果读到运算符,且 S2 栈顶也为运算符,且当前运算符优先级大于栈顶元素,则将当前运算符压入 S2 栈顶。因为后面读取到的运算符可能比当前运算符优先级更高,因此暂时不能输出当前运算符
  7. 如果读到运算符,且 S2 栈顶也为运算符,且当前运算符优先级小于等于栈顶元素,则将 S2 栈顶运算符弹出,加到 S1 栈尾。因为优先级高的运算符要先参加运算。注意,这是一个递归过程,因为 S2 中可能已存在多个运算符,它们的优先级可能都大于等于当前运算符,当这些运算符都弹出时,再将当前运算符压入 S2 栈顶
  8. 如果读到右括号,则将 S2 内首个左括号以上的运算符,全部加到 S1 栈尾。因为括号的优先级是最高的,立刻进行运算

例:中缀表达式 2*(3+5)+7/1-4 转换为后缀表达式

可以先转换为树,然后后序遍历得到后缀表达式,再和通过上面步骤推算出来的结果进行验证,判断是否正确。转换需要强调的是,我们用括号表示优先计算

表达式 2*(3+5)+7/1-4 中我们约定 * 和 / 的优先级高于 + 和 -,因此 + 和 - 要优先计算时需要加上括号。但是本身对于 + 和 - 来说,* 和 / 优先级高也是一种优先计算,优先计算就需要加上括号,只是我们一开始约定了先算 * 和 /,同时也为了方便,因此省略了括号

包括同级的 * 和 / 或 + 和 -,我们约定了从左往右算,其实先算左边的,也是一种优先计算,我们给优先计算的都加上括号,那么原式应为:((2*(3+5))+(7/1)) -4

强调这一点主要为了转换成树的时候方便划分左右子树,括号为一个子树的整体,这样一来转换成树的结构就很清晰了,[左子树 运算符 右子树]

后序遍历为:235+*71/+4-,即后缀表达式

此时再通过上面的步骤得到后缀表达式

可以看到最终结果也是 235+*71/+4-

详细代码见 3

3. 简易运算实现

Calculator 类

public class Calculator {

    private static final Map<String, Integer> OPERATORS = MapUtil.builder("+", 1).put("-", 1).put("*", 2).put("/", 2)
            .put("%", 2).put("^", 3).put("(", 0).put(")", 0).build();

    private Calculator() {
    }

    public static double calculate(String equation) {
        if (!BaseUtil.isWholeSymbol(equation)) {
            throw new IllegalArgumentException("请确认括号是否完整");
        }

        Deque<String> operand = new ArrayDeque<>();
        Deque<String> operator = new ArrayDeque<>();

        for (String str : toList(equation)) {
            if (NumberUtils.isCreatable(str)) {
                operand.push(str);
                continue;
            }

            Integer opt = OPERATORS.get(str);
            if (null == opt) {
                throw new IllegalArgumentException("操作符不合法");
            }

            if (StrPool.LBRACKET.value().equals(str) || operator.isEmpty() || opt > OPERATORS.get(operator.peek())) {
                operator.push(str);
            } else if (StrPool.RBRACKET.value().equals(str)) {
                // 判断是否是右括号, 存在右括号则运算符栈必有左括号, 即运算符栈不为空
                while (!operator.isEmpty()) {
                    if (StrPool.LBRACKET.value().equals(operator.peek())) {
                        operator.pop();
                        break;
                    } else {
                        String calculate = calculate(operator.pop(), operand.pop(), operand.pop());
                        operand.push(calculate);
                    }
                }
            } else if (opt <= OPERATORS.get(operator.peek())) {
                while (!operator.isEmpty() && opt <= OPERATORS.get(operator.peek())) {
                    String calculate = calculate(operator.pop(), operand.pop(), operand.pop());
                    operand.push(calculate);
                }
                operator.push(str);
            }
        }

        while (!operator.isEmpty()) {
            String calculate = calculate(operator.pop(), operand.pop(), operand.pop());
            operand.push(calculate);
        }
        return Double.parseDouble(operand.pop());
    }


    public static List<String> toList(String str) {
        List<String> list = new ArrayList<>();
        StringBuilder builder = new StringBuilder();

        String replace = str.replaceAll("\\s*", "");
        char[] chars = replace.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            boolean isMinus = '-' == chars[i] && (i == 0 || '(' == chars[i - 1]);
            if (isMinus) {
                builder.append(chars[i]);
                continue;
            }

            String val = String.valueOf(chars[i]);
            if (null != OPERATORS.get(val)) {
                if (StringUtil.INSTANCE.isNotBlank(builder)) {
                    list.add(builder.toString());
                }
                list.add(val);
                builder = new StringBuilder();
            } else {
                builder.append(chars[i]);
            }
        }

        if (StringUtil.INSTANCE.isNotBlank(builder)) {
            list.add(builder.toString());
        }
        return list;
    }

    private static String calculate(String operator, String val2, String val1) {
        double pre = Double.parseDouble(val1);
        double suf = Double.parseDouble(val2);

        switch (operator) {
            case "+":
                return pre + suf + "";
            case "-":
                return pre - suf + "";
            case "*":
                return pre * suf + "";
            case "/":
                return pre / suf + "";
            case "%":
                return pre % suf + "";
            case "^":
                return Math.pow(pre, suf) + "";
            default:
                return "0";
        }
    }
}

BaseUtil 类

public class BaseUtil {

    private static final Map<Character, Character> R_SYMBOL = MapUtil.builder(')', '(').put(']', '[').put('}', '{').build();

    private static final List<Character> L_SYMBOL = ListUtil.list('(', '[', '{');

    private BaseUtil() {
    }

    public static boolean isWholeSymbol(String str) {
        Deque<Character> symbol = new ArrayDeque<>();

        for (char ch : str.toCharArray()) {
            if (R_SYMBOL.containsKey(ch)) {
                if (symbol.isEmpty() || !symbol.peek().equals(R_SYMBOL.get(ch))) {
                    return false;
                }
                symbol.pop();
            } else if (L_SYMBOL.contains(ch)) {
                symbol.push(ch);
            }
        }

        return symbol.isEmpty();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/763765.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录day5 | 242.有效的字母异位词 349. 两个数组的交集 202.快乐数

文章目录 一、有效的字母异位词二、两个数组的交集三、快乐数 一、有效的字母异位词 242.有效的字母异位词 代码随想录知识点 哈西法可以选取的三种数据结构&#xff1a; 数组setmap class Solution { public:bool isAnagram(string s, string t){int hash[26] {0};// 1f…

CMS系统访问权限限制

创建一些全局的通用方法 const USER_KEY "USER_KEY" const TOKEN_KEY "JWT_TOKEN_KEY"class Auth {constructor() {this.token nullthis.user nullthis.token localStorage.getItem(TOKEN_KEY)const userJson localStorage.getItem(USER_KEY)if (use…

Redis连接报错:ERR Client sent AUTH, but no password is set

如果在redis.windows.conf或者redis.conf&#xff08;我的是这个配置文件&#xff09; 文件夹中设置了密码&#xff0c;但是会报错 ERR Client sent AUTH, but no password is set 用记事本打开redis.windows.conf或者redis.conf &#xff08;我的是这个配置文件&#xff09;…

一次性讲清楚常考面试题:进程和线程的区别

进程是程序的一次动态执行&#xff0c;它对应着从代码加载&#xff0c;执行至执行完毕的一个完整的过程&#xff0c;是一个动态的实体&#xff0c;它有自己的生命周期。它因创建而产生&#xff0c;因调度而运行&#xff0c;因等待资源或事件而被处于等待状态&#xff0c;因完成…

今天实习第三天,vue(vue-cli部分,webpack部分,vue-router部分,elementUI部分)

01.创建第一个vue-cli。这里用的是node.js。早上的时候&#xff0c;就需要把node.js安装上去 02.node.js安装 第一步.去官网下载node.js https://nodejs.org/en 第二步.运行官网下载的node.js的msi文件&#xff08;记住所有的node.js文件的安装包都是msi文件的形式&#xff0…

(学习笔记-TCP连接建立)IP层会分片,为什么TCP层还需要MSS呢?

前提知识&#xff1a; 网络层最常用的是IP协议&#xff0c;IP协议会将传输层的报文作为数据部分&#xff0c;再加上IP包头组装成IP报文&#xff0c;如果IP报文大小超过了MTU(1500字节)就会再次分片&#xff0c;得到一个即将发送到网络的IP报文 MTU和MSS: MTU&#xff1a;一个网…

如何在 Excel 中快速生成随机密码?

有时&#xff0c;我们可能想创建随机密码来保护某些重要内容。 但是&#xff0c;您有什么技巧可以在Excel中快速生成随机密码&#xff1f; 在这里&#xff0c;我有一些可以在Excel工作表中处理的方法。 用公式生成随机密码 使用插入随机数据生成随机密码​编辑 用公式生成随机…

普通人的姓名可以注册为商标吗?

商标是商品的生产者、经营者在其生产、制造、加工或者经销的商品上或者服务使用的标志&#xff0c;用于区别商品或服务来源。商标由文字、图形、字母、数字、三维标志、颜色组合和声音等组合而成&#xff0c;以姓名注册商标属于文字商标&#xff0c;因此&#xff0c;个人的名字…

第十二章:MULTI-SCALE CONTEXT AGGREGATION BY DILATED CONVOLUTIONS——通过膨胀卷积的多尺度上下文聚合

0.摘要 目前用于语义分割的先进模型是基于最初设计用于图像分类的卷积网络的改进。然而&#xff0c;像语义分割这样的密集预测问题在结构上与图像分类不同。在这项工作中&#xff0c;我们开发了一个专门为密集预测设计的新的卷积网络模块。所提出的模块使用膨胀卷积来系统地聚合…

QQ号码3个月未登陆真的要回收?

7月17日消息&#xff0c;微信号长期未使用会被回收的消息引起热议。 腾讯微信团队微博发文称&#xff1a;为保障用户的微信账号安全&#xff0c;注册后不活跃&#xff0c;长期未登录&#xff0c;并且没有零钱的微信账号&#xff0c;会被系统注销&#xff0c;无法使用。 不过也有…

B. The BOSS Can Count Pairs

Problem - 1830B - Codeforces 思路&#xff1a;因为ai*ajbibj&#xff0c;bibj<2*n&#xff0c;那么会有ai*aj<2*n&#xff0c;那么会有min(ai,aj)<sqrt(2*n)&#xff0c;我们能够发现我们只要枚举ai&#xff08;假设ai<aj&#xff09;那么只要在这种情况下求得所…

Kubernetes(K8s)常用命令大全:熟练编排更完美

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

提升效率,打通万里牛ERP与下游用友U8财务软件的无缝对接

一、对接流程 1.1 销售/售后流程 在万里牛订单出库后&#xff0c;通过轻易云数据集成平台将数据推送至用友U8销售订单和销售出库单&#xff0c;这些单据可以进行关联操作。 当万里牛售后单完成退货入库后&#xff0c;通过数据集成平台将数据推送至用友U8销售退货单和红字销售…

基于SpringBoot+vue的点餐平台网站设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

编程导航算法通关村第 1关 | 单链表的操作

编程导航算法通关村第 1关 | 链表的操作 文章目录 编程导航算法通关村第 1关 | 链表的操作单链表链表的定义初始化链表的遍历获取链表的长度链表的插入链表的节点的删除 双向链表节点的定义双向链表的定义节点的打印获取长度头部插入元素尾部插入元素链表的删除 单链表 链表的…

InnoDB锁内存结构

假如说 我们SELECT * FROM table WHERE a < 10000 FOR UPDATE 那岂不是要加几万条锁 这消耗老鼻子内存了 这些锁有很多地方都是一样的啊 能不能通过某种方式整理整理节省点内存呢? 答案是能 如果符合下边这些条件&#xff1a; 在同一个事务中进行加锁操作 被加锁的记录…

Redis远程字典服务

目录 前言 1.NoSQL 1.1NOSQL和关系型数据库比较 1.2非关系型数据库的优势 1.3关系型数据库的优势 ​编辑 2.主流的NOSQL产品 键值(Key-Value)存储数据库 列存储数据库 文档型数据库 图形(Graph)数据库 3.Redis简介 redis的应用场景 4.命令操作 4.1字符串类型 s…

python根据excel数据,基于散点图绘制棋盘图

文章目录 一、需求二、处理方式三、代码实现 一、需求 根据可视化的需要&#xff0c;下图的数据需要使用棋盘图的样式来展示&#xff0c; 原始数据&#xff1a; 最终效果图&#xff1a; 二、处理方式 1、先将DataFrame数据转换为Numpy数组&#xff1b; 2、先使用np.transp…

Window下Mysql5.x和8.x版本切换

在最近的工作中需要使用Mysql5.x版本&#xff0c;但以前的自己项目开发使用的Mysql版本都是8.x。因此特意去查资料看了一下&#xff0c;下边是如何在window下实现两个不同版本Mysql的切换。 既然是不同的Mysql&#xff0c;所以我们需要去下载自己需要的Mysql版本安装即可。&…

立思辰打印机IP连接和USB连接

打印机是常用办公设备,很多家庭也都用着这个,立思辰3032是一个比较老的型号,属于国产打印机,基本功能都有,下边分享一下安装经验: 1.驱动: 不管是连USB还是IP地址,都需要先给电脑装上打印机驱动,只有装上驱动,系统才能够识别打印机,并帮助用户传输文件。一般来讲,立…