波兰表达式 & 逆波兰表达式

2023-11-13

1、概述

1.1、什么是波兰表达式

先来看看维基百科对于波兰表达式和逆波兰表单的解释:

波兰表示法(Polish notation,或波兰记法),是一种逻辑、算术和代数表示方法,其特点是操作符置于操作数的前面,因此也称做前缀表示法。如果操作符的元数(arity)是固定的,则语法上不需要括号仍然能被无歧义地解析。

逆波兰表示法(Reverse Polish notation,RPN,或逆波兰记法),是一种是由波兰数学家扬·武卡谢维奇1920年引入的数学表达式形式,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。

这个解释听起来有点难理解。波兰表达式也称为前缀表达式,逆波兰表达式也称为后缀表达式。以表达式 (1 + 2) * (3 + 4) 为例,这个表达式称为中缀表达式。

表达式 描述 结果
前缀表达式 不含括号的的算数表达式,将运算符写在前面,操作数写在后面 * + 1 2 + 3 4
中缀表达式 必须含括号,操作符处于操作数的中间 ( 1 + 2 ) * ( 3 + 4 )
后缀表达式 不含括号,运算符放在两个运算对象的后面。 1 2 + 3 4 + *

与中缀表达式相比,前缀表达式和后缀表达式有个好处,没有圆括号,在计算的时候无需考虑优先级,能够提高计算机的运算效率。

1.2、相互转化

中缀表达式得出的前缀后缀表达式不唯一,只要不破坏中缀表达式里本身的运算优先级,就能得到与中缀表达式得到一致的结果。

  • 将中缀表达式依据优先级关系括起来,将运算符移到对应括号的前方,去掉括号,即得波兰表达式。
  • 将中缀表达式依据优先级关系括起来,将运算符移到对应括号的后方,去掉括号,即得逆波兰表达式。

举例说明:

中缀表达式 : a + b

前缀表达式 : + a b

后缀表达式 :  a b + 
中缀表达式 : ( a + b ) * c + d - ( e + g ) * h

前缀表达式 : 

1、 (( a + b ) * c + d ) - (( e + g ) * h )  // 变成 2 个表达式

2、 - r s  // 记 r = ( a + b ) * c + d   s = ( e + g ) * h 

3、 对于 r ,同样添加圆括号,变成 2 个子表单式  ( ( a + b ) * c ) + d   

    3.1  + ( a + b ) * c d
    
    3.2  + * ( a + b ) c d
    
    3.3  + * +  a b c d
    
4、 对于 s , ( e + g ) * h 

    4.1  * ( e + g ) h
    
    4.2  * + e g h
    
5、 最终结果 - + * + a b c d * + e g h


后缀表达式 :  a b + c * d + e g + h * -  ( 后缀表达式的推到过程与前缀表达式推导类似,只不过运算符已到括号后方)

2、逆波兰表达式计算 - 算法

  以 LeetCode 的第 150 题为例 。 在该例子中,tokens 即为后缀表达式(逆波兰表达式)

输入: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

  利用逆波兰表达式计算时,使用一个栈来存储操作数,从左到右遍历逆波兰表达式,进行如下操作:

  • 如果遇到操作数,则将操作数入栈;
  • 如果遇到运算符,则将两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数入栈;
  • 遍历完成后,栈内只有一个元素,该元素即为逆波兰表达式的值。

  如果利用波兰式来计算表单式的话,对操作的的遍历从后到前,同时出栈操作,第一个操作是左操作数,第二个操作数是右操作数。

  官方代码如下:

var evalRPN = function(tokens) {
    const stack = [];
    const n = tokens.length;
    for (let i = 0; i < n; i++) {
        const token = tokens[i];
        if (isNumber(token)) {
            stack.push(parseInt(token));
        } else {
            const num2 = stack.pop();
            const num1 = stack.pop();
            if (token === '+') {
                stack.push(num1 + num2);
            } else if (token === '-') {
                stack.push(num1 - num2);
            } else if (token === '*') {
                stack.push(num1 * num2);
            } else if (token === '/') {
                stack.push(num1 / num2 > 0 ? Math.floor(num1 / num2) : Math.ceil(num1 / num2));
            }
        }
    }
    return stack.pop();
};

const isNumber = (token) => {
    return !('+' === token || '-' === token || '*' === token || '/' === token );
}

  更简洁的版本:

var evalRPN = function(tokens) {
    const stack=[];
    const obj = {
      '+': (a, b) => +b + +a,
      '-': (a, b) => b - a,
      '*': (a, b) => b * a,
      '/': (a, b) => b / a | 0
    };
    tokens.forEach((v)=>{
        stack.push(isNaN(v)?obj[v](stack.pop(),stack.pop()):v);
    })
    return stack[0];
};

3、中缀表达式计算 - 算法

  以上边的 LeetCode 的实例继续讲解,如果用中缀表单式来计算的,该如何操作:

对应的前缀表达式: ( ( ( 10 * ( 6 / ( ( 9 + 3 ) * -11 ) ) ) + 17 ) + 5 )
为了与上例中格式保持一致,对中缀表达式转换成数组格式:

tokens = ["(", "(", "(", "10", "*", "(", "6", "/", "(", "(", "9", "+", "3", ")", "*", "-11", ")", ")", ")", "+", "17", ")", "+", "5", ")"]

解决思路

  1. 初始化 2 个栈,一个用来存储运算符 S1, 一个用来存储操作数 S2;
  2. 遇到左括号、操作符直接入栈S1,遇到数字直接入栈S2;
  3. 遇到右括号,S1依次弹出操作符,S2弹出2个操作数,先弹出的作为右操作数,后弹窗的左右左操作数,并将执行结果重新入栈S2;
  4. 直至S1全部入栈,此时S2中只有一个数据,即为计算的结果。

算法如下

var evalMiddle = function (tokens) {
  const s1 = []  //用于存储运算符
  const s2 = []  //用于存储操作数
  const n = tokens.length
  for (let i = 0; i < n; i++) {
    const token = tokens[i]
    if (isNumber(token)) {
      s2.push(parseInt(token))
    } else {
      if (token === ')') {
        // 弹出操作数
        const num2 = s2.pop()
        const num1 = s2.pop()
        // 弹出操作符,同时弹出左括号
        const _token = s1.pop()
        const leftParentheses = s1.pop()

        if (_token === '+') {
          s2.push(num1 + num2)
        } else if (_token === '-') {
          s2.push(_token - num2)
        } else if (_token === '*') {
          s2.push(num1 * num2)
        } else if (_token === '/') {
          s2.push(num1 / num2 > 0 ? Math.floor(num1 / num2) : Math.ceil(num1 / num2))
        }
      } else {
        s1.push(token)
      }
    }
  }
  return s2.pop()
}

var isNumber = (token) => {
  return !('+' === token || '-' === token || '*' === token || '/' === token || '(' === token || ')' === token)
}

var tokens = ["(", "(", "(", "10", "*", "(", "6", "/", "(", "(", "9", "+", "3", ")", "*", "-11", ")", ")", ")", "+", "17", ")", "+", "5", ")"]

console.log(evalMiddle(tokens))

 由以上代码可知,通知中缀表达式计算时,为了能更好的阐述表单式之间的的优先级时,需要通过2个堆栈来维护。
在上述算法中,针对中缀表达式的每个子表单式都添加括号,在实际过程中,我们针对上例的通常是如下写法,则在中缀表达式计算时可能还要考虑符号的优先级,算法相对会更加复杂一些。

 10 * (6 / ((9 + 3) * -11)) + 17) + 5
 
 而不是
 
 ( ( ( 10 * ( 6 / ( ( 9 + 3 ) * -11 ) ) ) + 17 ) + 5 )
 

4、总结

 中缀表达式更利于显示世界中的计算,他表达跟清晰;前、后缀表达式更利于计算机计算,因为他只需要一个数据栈就能够解决计算结果。

 关于中缀表达式和前缀表达式、后缀表单式之间的相互转换算法,本质上与中缀表单式计算的算法差不多,仍旧需要一个栈存储操作数、一个栈存储操作符,有兴趣的可以执行编码实现。

参考文献:

https://www.bilibili.com/video/BV1aJ411i7G7?from=search&seid=15042777190091941319

https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/solution/ni-bo-lan-biao-da-shi-qiu-zhi-by-leetcod-wue9/

https://juejin.cn/post/6844903750994231303#heading-4

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

波兰表达式 & 逆波兰表达式 的相关文章

随机推荐

  • Mayor‘s posters(线段树染色)

    题目链接 Mayor s posters 2023 4 13 更新了代码 修复了错误的离散化长度 已在代码中注出 大致题意 有n个人依次贴海报 第i个海报的范围是 li ri 后面贴的海报会覆盖掉之前贴的海报 问 最终还能看到多少张海报 解
  • java的3大注释快捷键大全

    单行注释 行注释 一般用于单行或者少量代码 快捷键 光标 ctrl 或者 多行注释 块注释 一般用于多行或者大量代码 快捷键 选中 ctrl shift 文档注释 方法或类注释 一般用于对类和方法的说明 快捷键 光标 回车键 EnTer
  • 蓝桥杯Java必备基础知识总结大全【3W字】持续更新中

    本文会持续更新 如果对您有帮助的话可以点点关注 双击 本人2021年蓝桥杯C B组国二 今年转战Java 并整理此文 希望能够对大家有所帮助 第一次写这么长的文章 可能有的地方写的不是很好 还请大家多多谅解 我会持续进行改进并且更新 更新
  • 海思(MPP)媒体处理软件平台(3)-----VDEC

    sample vdec 视频解码 测试环境 在HI3531D开发板上运行 查看代码使用VSCode 运行 nfsroot mpp sample vdec sample vdec please choose the case which yo
  • [激光原理与应用-68]:光耦继电器、固态继电器、延时继电器、PLC开关信号

    一 PLC开关信号 备注 PLC需要的是开关信号 闭合或断开 不需要TTL电平信号 二 激光器输出信号 三 固态激光器工作原理 四 光耦继电器原理 五 光耦 延时 继电器原理 六 差分转单端原理
  • 排列组合知识

    排列组合的定义 排列 就是从n个元素中取出m个元素 按照一定的顺序排成一列 看到没 是要有顺序排成一列的 组合 也是从n个元素中取出m个元素 只不过是组成一个组 并不要求排成一列 只要组的成员不同就可以了 公式如下 例题 哪里用得上排列组合
  • 【Markdown】图片缩放

    01 原图表示 语法为 替代文本 图片链接地址 其中 替代文本是在无法显示图片时显示的替代文本 而图片链接是指向图片的URL或相对路径 例如 插入Panda图片 panda https img blog csdnimg cn e5f32e4
  • 亚信科技AntDB数据库专家参加向量数据库首次技术标准研讨会

    2023年7月19日下午 中国通信标准化协会大数据技术标准推进委员会数据库与存储工作组 CCSA TC601 WG4 联合中国信通院数据库应用创新实验室 CAICT DBL 在线上召开 向量数据库技术要求 标准首次研讨会 本次会议由中国信通
  • 单端反激——隔离型DC/DC变换器的设计及仿真

    单端反激 隔离型DC DC变换器的设计及仿真 技术指标 1 原理分析 2 参数设计 3 仿真验证 技术指标 输入电压 V s m i n
  • Spring Boot 2.2.6 源码之旅二十五SpringMVC源码之RequestMappingHandlerMapping的初始化三

    Spring Boot 2 2 6 源码之旅二十五SpringMVC源码之RequestMappingHandlerMapping的初始化三 简单流程图 MappingRegistry的一些映射 urlLookup一键多值的url和Requ
  • 那些会阻碍程序员成长的细节[4]

    照例 如果没有读过之前的系列 在这里可以先回顾一下 那些会阻碍程序员成长的细节 1 那些会阻碍程序员成长的细节 2 那些会阻碍程序员成长的细节 3 本文共 1637 字 预计阅读时间 5 分钟 不愿意跟领导走的近 是不是有这样的体会 凡事有
  • 【python标准库学习】re模块

    1 什么是re 正则表达式一门相对通用的语言 在python中也有对正则表达式的支持 那就是的内置re模块 正则表达式就是一系列的规则去匹配字符串然后进行相应的操作 这些规则网上一搜一大片 而re则是运用正则表达式来提供一系列的功能强大的接
  • Vue中如何进行打包与部署?

    Vue中如何进行打包与部署 Vue是一款流行的JavaScript框架 它提供了丰富的功能和组件 可以用于构建现代化的Web应用程序 在开发Vue应用程序时 我们通常需要进行打包和部署 本文将介绍Vue中的打包和部署 包括使用Webpack
  • STL list合并

    知识点来源 cplusplus STL list 网上很多关于list的操作很少有提及到怎么合并 要说这个合并几乎是每个数据结构课提及到的O 1 操作的必修知识点 同时还有人甚至搞不清楚什么叫Merge 归并 和合并 Union 归并的意思
  • linux 查看端口连接数

    一 查看哪些IP连接本机 netstat an 二 查看TCP连接数 1 统计80端口连接数 netstat nat grep i 80 wc l 2 统计httpd协议连接数 ps ef grep httpd wc l 3 统计已连接上的
  • 高斯列主消元法 求非齐次线性方程组 C语言实现代码

    高斯列主元素消去法是由高斯消去法改进的算法 下面浅浅分享一下本人对该方法的理解 Ax b 先说高斯消去法 感觉基本的思路就跟我们手算非齐次线性方程组差不多 在线性代数中 我们求解方程组都是这种思路 消元的过程相当于是 由系数矩阵A和非齐次项
  • linux下代码分析工具Splint

    1 C代码静态分析工具 Its4 读取一个或多个 C C 源程序 将每个源程序分割成函数标志流 然后检查生成的标志是否存在于漏洞数据库中 从而得到每个源程序的所有错误警告列表 并带有相关的描 述 其规则库vulns i4d定义了各种函数的危
  • 【医学图像分割】 MIXED Transformer 、DS-TransUNet、Swin-Unet

  • Qt开发北斗定位系统融合百度地图API及Qt程序打包发布

    Qt开发北斗定位系统融合百度地图API及Qt程序打包发布 1 上位机介绍 最近有个接了一个小型项目 内容很简单 就是解析北斗GPS的串口数据然后输出经纬度 但接过来觉得太简单 就发挥了主观能动性 增加了百度地图API 不但能实时定位 还能在
  • 波兰表达式 & 逆波兰表达式

    1 概述 1 1 什么是波兰表达式 先来看看维基百科对于波兰表达式和逆波兰表单的解释 波兰表示法 Polish notation 或波兰记法 是一种逻辑 算术和代数表示方法 其特点是操作符置于操作数的前面 因此也称做前缀表示法 如果操作符的