波兰表达式 & 逆波兰表达式-程序员宅基地

技术标签: JavaScript  javascript  

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zcs425171513/article/details/118310303

智能推荐

Linux查看登录用户日志_怎么记录linux设备 发声的登录和登出-程序员宅基地

文章浏览阅读8.6k次。一、Linux记录用户登录信息文件1  /var/run/utmp----记录当前正在登录系统的用户信息;2  /var/log/wtmp----记录当前正在登录和历史登录系统的用户信息;3  /var/log/btmp:记录失败的登录尝试信息。二、命令用法1.命令last,lastb---show a listing of la_怎么记录linux设备 发声的登录和登出

第四章笔记:遍历--算法学中的万能钥匙-程序员宅基地

文章浏览阅读167次。摘要:1. 简介 2. 公园迷宫漫步 3. 无线迷宫与最短(不加权)路径问题 4. 强连通分量1. 简介在计算机科学裡,树的遍历(也称为树的搜索)是圖的遍歷的一种,指的是按照某种规则,不重复地访问某种樹的所有节点的过程。具体的访问操作可能是检查节点的值、更新节点的值等。不同的遍历方式,其访问节点的顺序是不一样的。两种著名的基本遍历策略:深度优先搜索(DFS) 和 广度优先搜索(B...

【案例分享】使用ActiveReports报表工具,在.NET MVC模式下动态创建报表_activereports.net 实现查询报表功能-程序员宅基地

文章浏览阅读591次。提起报表,大家会觉得即熟悉又陌生,好像常常在工作中使用,又似乎无法准确描述报表。今天我们来一起了解一下什么是报表,报表的结构、构成元素,以及为什么需要报表。什么是报表简单的说:报表就是通过表格、图表等形式来动态显示数据,并为使用者提供浏览、打印、导出和分析的功能,可以用公式表示为:报表 = 多样的布局 + 动态的数据 + 丰富的输出报表通常包含以下组成部分:报表首页:在报表的开..._activereports.net 实现查询报表功能

Ubuntu18.04 + GNOME xrdp + Docker + GUI_docker xrdp ubuntu-程序员宅基地

文章浏览阅读6.6k次。最近实验室需要用Cadence,这个软件的安装非常麻烦,每一次配置都要几个小时,因此打算把Cadence装进Docker。但是Cadence运行时需要GUI,要对Docker进行一些配置。我们实验室的服务器运行的是Ubuntu18.04,默认桌面GNOME,Cadence装进Centos的Docker。安装Ubuntu18.04服务器上安装Ubuntu18.04的教程非常多,在此不赘述了安装..._docker xrdp ubuntu

iOS AVFoundation实现相机功能_ios avcapturestillimageoutput 兼容性 ios17 崩溃-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏2次。首先导入头文件#import 导入头文件后创建几个相机必须实现的对象 /** * AVCaptureSession对象来执行输入设备和输出设备之间的数据传递 */ @property (nonatomic, strong) AVCaptureSession* session; /** * 输入设备 */_ios avcapturestillimageoutput 兼容性 ios17 崩溃

Oracle动态性能视图--v$sysstat_oracle v$sysstat视图-程序员宅基地

文章浏览阅读982次。按照OracleDocument中的描述,v$sysstat存储自数据库实例运行那刻起就开始累计全实例(instance-wide)的资源使用情况。 类似于v$sesstat,该视图存储下列的统计信息:1>.事件发生次数的统计(如:user commits)2>._oracle v$sysstat视图

随便推点

Vue router报错:NavigationDuplicated {_name: "NavigationDuplicated", name: "NavigationDuplicated"}的解决方法_navigationduplicated {_name: 'navigationduplicated-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏9次。我最近做SPA项目开发动态树的时候一直遇到以下错误:当我点击文章管理需要跳转路径时一直报NavigationDuplicated {_name: “NavigationDuplicated”, name: “NavigationDuplicated”}这个错误但是当我点击文章管理后,路径跳转却是成功的<template> <div> 文章管理页面 <..._navigationduplicated {_name: 'navigationduplicated', name: 'navigationduplic

Webrtc回声消除模式(Aecm)屏蔽舒适噪音(CNG)_webrtc aecm 杂音-程序员宅基地

文章浏览阅读3.9k次。版本VoiceEngine 4.1.0舒适噪音生成(comfort noise generator,CNG)是一个在通话过程中出现短暂静音时用来为电话通信产生背景噪声的程序。#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS)static const EcModes kDefaultEcMode = kEcAecm;#elsestati..._webrtc aecm 杂音

医学成像原理与图像处理一:概论_医学成像与图像处理技术知识点总结-程序员宅基地

文章浏览阅读6.3k次,点赞9次,收藏19次。医学成像原理与图像处理一:概论引言:本系列博客为医学成像原理与图像处理重要笔记,由于是手写,在此通过扫描录入以图片的形式和电子版增补内容将其进行组织和共享。前半部分内容为图像处理基础内容,包括图像的灰度级处理、空间域滤波、频率域滤波、图像增强和分割等;后半部分内容为医学影象技术,包括常规胶片X光机、CR、DR、CT、DSA等X射线摄影技术、超声成像技术、磁共振成像(MRI)技术等。本篇主要内容是概论。_医学成像与图像处理技术知识点总结

notepad++ v8.5.3 安装插件,安装失败怎么处理?下载进度为0怎么处理?_nodepa++-程序员宅基地

文章浏览阅读591次,点赞13次,收藏10次。notepad++ v8.5.3 安装插件,下载进度为0_nodepa++

hive某个字段中包括\n(和换行符冲突)_hive sql \n-程序员宅基地

文章浏览阅读2.1w次。用spark执行SQL保存到Hive中: hiveContext.sql(&quot;insert overwrite table test select * from aaa&quot;)执行完成,没报错,但是核对结果的时候,发现有几笔数据超出指定范围(实际只包含100/200)最终排查到是ret_pay_remark 字段包含换行符,解决方案:执行SQL中把特殊字符替换掉regexp_replace(..._hive sql \n

印象笔记05:如何打造更美的印象笔记超级笔记_好的印象笔记怎么做的-程序员宅基地

文章浏览阅读520次,点赞10次,收藏8次。印象笔记05:如何打造更美的印象笔记超级笔记本文介绍印象笔记的具体使用,如何打造更美更实用的笔记。首先想要笔记更加好看和实用,我认为要使用超级笔记。所谓超级笔记就是具有很多便捷功能的笔记。_好的印象笔记怎么做的

推荐文章

热门文章

相关标签