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 d3.2  + * ( a + b ) c d3.3  + * +  a b c d4、 对于 s , ( e + g ) * h 4.1  * ( e + g ) h4.2  * + e g h5、 最终结果 - + * + 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.lengthfor (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

波兰表达式 逆波兰表达式相关推荐

  1. 数据结构:前缀,中缀,后缀表达式(逆波兰表达式)

    前缀表达式(波兰表达式) 前缀表达式的运算符位于操作数之前. 比如 (1+2)*3-4  对应的前缀表达式就是: - * + 1 2 3 4 前缀表达式的计算机求值 从右至左扫描表达式,遇到数字时,就 ...

  2. 数组结构与算法-036-042 前中后缀表达式-逆波兰计算器

    036 前缀 中缀 后缀(逆波兰表达式)表达式 前缀表达式 前缀表达式(波兰表达式) 前缀表达式又称波兰表达式,前缀表达式的运算符位于操作数之前 举例说明:(3 + 4) * 5 -6 对应的前缀表达 ...

  3. 数据结构:后缀表达式(逆波兰表达式)

    逆波兰表达式计算 package com.atchina.stack;import java.util.ArrayList; import java.util.List; import java.ut ...

  4. 四则运算表达式 逆波兰表达式

    感觉是一块比较偏门的东西,特此记录下: 逆波兰表达式: 或者叫后缀表达式,我们原先用的一直都是中缀表达式,但是因为如果表达式过长,计算机计算起来不方便.需要先遍历一边,找出其中的()进行优先计算,如果 ...

  5. java 逆波兰_逆波兰表达式算法-Java版

    这两天一直在看数据结构,栈这个地方,基础的就是这个逆波兰表达式,看了很多博文,都讲得不清不楚或者只能计算一个位的数字,决定自己写,这篇博文给了很大启发–>Go New Land AND Here ...

  6. php 逆波兰 if,逆波兰算法在规则引擎中的运用

    前言 逆波兰表示法(Reverse Polish notation,RPN,或逆波兰记法),是一种是由波兰数学家扬·武卡谢维奇1920年引入的数学表达式方式,在逆波兰记法中,所有操作符置于操作数的后面 ...

  7. 简单计算器 逆波兰表达式

    问题 C: 悠派计算器 时间限制: 1 Sec  内存限制: 128 MB 提交: 7  解决: 2 [提交][状态][讨论版][命题人:qianyouyou] 题目描述 yoyo的小老弟小渣渣灰特别 ...

  8. 逆波兰表达式简单介绍

    逆波兰表达式又叫做后缀表达式.在通常的表达式中,二元运算符总是置于与之相关的两个运算对象之间,所以,这种表示法也称为中缀表示.波兰逻辑学家J.Lukasiewicz于1929年提出了另一种表示表达式的 ...

  9. 数据结构-栈应用之逆波兰表达式(后缀表达式)

    逆波兰表达式含义我就不做赘述了,摘自百科上的一段话: 逆波兰表达式又叫做后缀表达式.在通常的表达式中,二元运算符总是置于与之相关的两个运算对象之间,这种表示法也称为中缀表示.波兰逻辑学家J.Lukas ...

  10. 逆波兰表达式[栈 C 语言 实现]

    逆波兰表达式 逆波兰表达式又叫做后缀表达式.在通常的表达式中,二元运算符总是置于与之相关的两个运算对象之间,这种表示法也称为中缀表示.波兰逻辑学家J.Lukasiewicz于1929年提出了另一种表示 ...

最新文章

  1. Linux: debian,ubuntu命令行安装chrome/chromium
  2. mysql like ilike_MySQLilike 子句
  3. 手动配置WCF宿主的.config文件遇到的几种错误
  4. 加减乘除html代码vb,VB做加减乘除的代码是什么啊? 或者是怎么做加减乘除 ?...
  5. 哈夫曼树(最优树)及编码
  6. 贪吃蛇“大作战”(二)
  7. Scroller的使用及解析(滑动删除)
  8. 总结iPhone、iPad各机型对应的iOS 系统版本(最全)
  9. 华为桌面云虚拟机卸载VM Tools工具计算机蓝屏开机不了-完美解决
  10. 一道反序列化的CTF题分享
  11. win7系统笔记本架设无线热点(AP)
  12. 从零开始的DIY智能家居--自己在家手搓一套人脸识别智慧猫眼系统
  13. Zircon传奇3服务器修改,最详细的传奇3GM命令
  14. 报表模块-report
  15. stata回归表格输出
  16. java基于微信小程序的个人管理软件 uniapp 小程序
  17. 基于PaddlePaddle和PaddleHub的口罩检测系统的落地实现
  18. 文件 md5去重 linux,对比文件md5值实现去重文件
  19. 什么时候使用接口,什么时候使用抽象类
  20. Interview Project50 50/11

热门文章

  1. webgl 地球_使用WebGL和JavaScript构建地球
  2. 操作系统课程设计任务书
  3. 香农编码用matlab实验报告,香农编码实验报告
  4. 浅谈谷歌退出中国市场带来的问题
  5. SQL Server2012备份到网络驱动器
  6. linux下查看计划任务,linux查看计划任务.docx
  7. 百度贴吧签到助手php,玩心太大,自己部署一个贴吧签到器
  8. 【栈】实现逆波兰计算器
  9. maya城市汽车流光效果插件car motion light 1.0.1下载及教程
  10. “非常晚餐”第一期 “3D虚拟世界”是“黑客帝国”吗?