js实现 - 逆波兰式

没有任何方法,除非你才华横溢。
——艾略特


js实现 - 逆波兰式

2019-05-26 by 文科生

最近编译原理实验有涉及到逆波兰式,而且听闻有人在前端面试过程中被问到逆波兰式算法的实现,之前的离散数学课程中也有涉及到逆波兰式,作为一名前端人员,终于按耐不住想用js去实现求逆波兰式的算法。我查阅了大量的资料,发现有的算法虽然基本实现了对应的功能,但在细节处理方面略显不妥;而有的算法写的过于庞杂,想要完全读懂则代价昂贵,而且代码的臃肿不是因为算法本身复杂,而是加入了一些算符,这对理解算法本质是不利的。于是我干脆自己用js写一遍该算法,目的是为了整理自己的思想,将该算法的本质呈现给位前端爱好者,希望大家面试顺利!本文将实现:

  • 普通算术表达式转换为逆波兰式
  • 求逆波兰式的值

上图demo可以去本人github下载

一、逆波兰式定义

将运算对象写在前面,而把运算符号写在后面。用这种表示法表示的表达式也称做后缀式。逆波兰式的特点在于运算对象顺序不变,运算符号位置反映运算顺序。

二、算法描述

根据普通算术表达式求逆波兰式:

  1. 首先构造一个运算符栈,此运算符在栈内遵循越往栈顶优先级越高的原则。
  2. 读入一个用中缀表示的简单算术表达式,为方便起见,设该简单算术表达式的右端多加上了优先级最低的特殊符号“#”。
  3. 从左至右扫描该算术表达式,从第一个字符开始判断,如果该字符是数字,则分析到该数字串的结束并将该数字串直接输出。
  4. 如果不是数字,该字符则是运算符,此时需比较优先关系。
    做法如下:将该字符与运算符栈顶的运算符的优先关系相比较。如果,该字符优先关系高于此运算符栈顶的运算符,则将该运算符入栈。倘若不是的话,则将此运算符栈顶的运算符从栈中弹出,将该字符入栈。
  5. 重复上述操作,直至扫描完整个简单算术表达式,确定所有字符都得到正确处理,我们便可以将中缀式表示的简单算术表达式转化为逆波兰表示的简单算术表达式。

计算逆波兰表达式的值:

  1. 构造一个栈,存放运算对象。
  2. 读入一个用逆波兰式表示的简单算术表达式。
  3. 自左至右扫描该简单算术表达式并判断该字符,如果该字符是运算对象,则将该字符入栈。若是运算符,如果此运算符是二目运算符,则将对栈顶部的两个运算对象进行该运算,将运算结果入栈,并且将执行该运算的两个运算对象从栈顶弹出。
  4. 重复上述操作直至扫描完整个简单算术表达式的逆波兰式,确定所有字符都得到正确处理,我们便可以求出该逆波兰算术表达式的值。

三、核心代码

// 适用于无符整数四则运算, 但运算结果可能是负数,如减法
(function () {
  'use strict'
  const rpn = {
    _precedence: {'/': 2, '*': 2, '-': 1, '+': 1, '#': 0},

    /**
     * operations
     * @private
     */
    _operation: {
      '+': (a, b) => (+a) + (+b),
      '-': (a, b) => (+a) - (+b),
      '*': (a, b) => (+a) * (+b),
      '/': (a, b) => (+a) / (+b)
    },

    /**
     * split expression to array
     * @private
     * @param exp - infix expression
     * @returns {Array|null}
     */
    _splitExp: function (exp) {
      return exp.match(/\d+|[^\d\s\t]/g);
    },

    /**
     * check a character, is or not an operator
     * @private
     * @param char - character
     * @return {boolean}
     */
    _isOperator: function (char) {
      return /^[\/\*\-\+#]$/.test(char);
    },

    /**
     * check character, is or not a bracket
     * @private
     * @param char - character
     * @retuens {boolean}
     */
    _isBracket: function (char) {
      return /^[\(\)]$/.test(char);
    },

    /**
     * check string, is or not a number
     * @private
     * @param str - character
     * @returns {boolean}
     */
    _isNumber: function (str) {
      return /^\d+$/.test(str);
    },

    /**
     * check exp, is or not a valid expression
     * @param {string} exp - expression
     * @returns {boolean} -
     */
    _isValidExpression: function (exp) { // 含有除数字、括号、操作符以外的符号即为非法
      return !/[^\d\s\t\+\-\*\/\(\)]/.test(exp);
    },

    /**
     * transfer infix expression to reverse polish notation
     * @param exp - infix express
     * @returns {string|null}
     */
    infix2rpn: function(exp) {
      if (!rpn._isValidExpression(exp)) return null;  // 用于保证以下处理的是合法的表达式

      var arrExp = rpn._splitExp(exp);  // 输入串分割
      var opStack = [];                 // 运算符栈
      var rpnStack = [];                // 存放逆波兰式结果

      arrExp = arrExp.concat('#');      // 加入最低优先级的算符 '#'

      var i,                        // 用于遍历arrExp
          item,                     // 遍历arrExp时暂存
          op,                       // 暂存opStack中的操作符
          len = arrExp.length;      // 记录arrExp长度
      for (i = 0; i < len; i ++) {
        item = arrExp[i];
        if (rpn._isNumber(item)) {
          rpnStack.push(item);
        } else if (rpn._isOperator(item)) {
          while (opStack.length) {
            op = opStack[opStack.length-1];        // push性能低于pop和数组按索引取值,要尽量避免push
            if(op === '(') {                // 栈顶运算符是左括号,需单独处理
              break;
            } else if (rpn._precedence[item] > rpn._precedence[op]) { // 否则,栈顶是运算符。并且如果...
              // 当前算符优先级大于算符栈栈顶优先级
              break;
            } else {                    // 当前算符优先级小于等于算符栈栈顶优先级
              rpnStack.push(opStack.pop()); // 弹出算符栈栈顶算符并放入逆波兰式结果栈中
            }
          }
          opStack.push(item);           // 将运算符压入
        } else {                        // item是括号
          if (item === '(') {           // 是 '('
            opStack.push(item);
          } else  {  // 否则,item是 ')'
            while (opStack[opStack.length-1] !== '(') {
              rpnStack.push(opStack.pop());
            }                   // ')' 遇 '(' ,相抵消
            opStack.pop();
          }
        }
      }
      return rpnStack.length ? rpnStack.join(' ') : null;
    },

    /**
     * calculate reverse polish notation - 本函数目前只支持二元运算
     * @param exp - reversed polish notation
     * @returns {number|null}
     */
    rpnCalculate: function (exp) {
      if (!rpn._isValidExpression(exp)) return null;  // 用于保证以下处理的是合法的表达式

      var arrExp = rpn._splitExp(exp);
      var calcStack = [];
      var item;                       // in arrExp
      var param1, param2;           // 运算对象

      var i, len = arrExp.length;
      for (i = 0; i < len; i ++) {
        item = arrExp[i];
        if (rpn._isNumber(item)) {
          calcStack.push(+item);    // 先将item转换为数值再压栈
        } else {                    // 否则item就是运算符
          param2 = calcStack.pop();
          param1 = calcStack.pop();
          calcStack.push(rpn._operation[item](param1, param2));// 执行运算并将结果压栈
        }
      }
      return calcStack.pop();
    },

    /**
     * calculate expression
     * @param exp - expression string
     * @returns {number|null}
     */
    calculate: function (exp) {
      return rpn.rpnCalculate(rpn.infix2rpn(exp));
    }
  }
  if (typeof module !== 'undefined' && module.exports) {
    module.exports = rpn;
  }

  if (typeof window !== 'undefined') {
    window.rpn = rpn;
  }
}());

四、总结

  1. 以上代码只支持实现四则运算,因为我在此只是想呈现算法的本质,如果要深入,那么就要考虑算符的特征等问题了(单目运算,双目运算等),这样本文就变得难以阅读了。
  2. 注意我没有把左、右括号看成是算符,虽然将左括号压入了栈中,但对左右括号有单独的处理方式。
  3. 由于本人学识有限,难免有错误之处,欢迎各位批评指导。

五、参考资源

javascript:逆波兰式表示法计算表达式结果

JavaScript中缀表达式转为逆波兰式(四则运算)

波兰式、逆波兰式与表达式求值

逆波兰表达式工具

原文地址:https://www.cnblogs.com/wen-k-s/p/10925987.html

时间: 2024-09-29 22:06:33

js实现 - 逆波兰式的相关文章

HDU1237 简单计算器 【栈】+【逆波兰式】

版本:1.0 日期:2014.5.17 2014.6.1 版权:© 2014 kince 转载注明出处 在介绍SwitchButton之前,先来看一下系统Button是如何实现的.源码如下: @RemoteView public class Button extends TextView { public Button(Context context) { this(context, null); } public Button(Context context, AttributeSet att

使用逆波兰式进行表达式求值

中缀表达式及后缀表达式图解中说明了使用逆波兰式进行表达式求值的方法,这里使用C++进行实现.实现和原理讲解有一点不同,需要进一步进行细化. 关于将中缀表达式转换成后后缀表达式的规则: 规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分:若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于找顶符号(乘除优先加减)则栈顶元素依次出找并输出,并将当前符号进栈,一直到最终输出后缀表达式为止. 上面的规则转换成下面的执行规则: 1.遇到操作数:直接输出(添加到后缀

逆波兰式与表达式求解

/*************** 逆波兰式即后缀表示法 预处理 ---- 中序表达式->逆序表达式(infix to postfix) 算法: while(表达式非空) if (遇到操作数) 直接输出 else if (遇到操作符op) op是( 直接入栈s op是) s.push输出,直到( op是四则运算,则 if (s为空 || s.top为( || op 优先级高于 s.top) op 入栈 else s.push输出 if (!s.empty) s.push输出 计算 算法: if (

python 逆波兰式

逆波兰式,也叫后缀表达式 技巧:为简化代码,引入一个不存在的运算符#,优先级最低.置于堆栈底部 class Stack(object): '''堆栈''' def __init__(self): self._stack = [] def pop(self): return self._stack.pop() def push(self, x): self._stack.append(x) 一.表达式无括号 def solve(bds): '''不带括号,引入#运算符''' pro = dict(

逆波兰式,有关栈的问题

/*有关逆波兰式的问题,也叫后缀表达式(将运算符写在操作数之后):例如:a+b的逆波兰式为a b +;下列程序是有关数字与数字相加的,将程序简单理解为,如果是数字,那么压入栈中,如果是运算符,那么出栈,将此前压入栈中的两个数取出栈并且相加,相加后再压入栈中,以此类推(按运算符的优先级!)*/ #include <stdio.h> #include <math.h>#include <stdlib.h>#include <ctype.h>#define STA

Haskell解决逆波兰式

摘自<Haskell趣学指南- Learn You a Haskell for Great Good> {- 逆波兰式(revese polish notation, RPN): 操作符出现在操作数的后面,而不是夹在它们中间. 如我们使用 "4 3 +" 而不是 "4 + 3". -} solveRPN :: String -> Double solveRPN = head . foldl foldingFunction [] . words wh

逆波兰式

在程序设计中,可能碰到需要对字符串数学表达式求值的问题,常用的方法是解析表达式,生成二叉树,然后进行计算.编译器就是使用这种方法来解析程序中的表达式的.这种方法实现起来有点难度,需要考虑运算符的优先级,括号的配对,堆栈的使用等等.我们正常情况下看到的数学表达式如果用二叉树遍历的话,恰好是中序遍历,故叫做中序表达式.除此之外,还有前序表达式,后序表达式.如:a+b+c(中序),++abc(前序),ab+c+(后序),如果表达式含有×,/,()等就更复杂了. 后缀表达式也称逆波兰表达式 因其使表达式

codechef Transform the Expression 转换成逆波兰式

把一般式子转换成逆波兰式. 这里的都是加括号的,难度降低点. Example Input: 3 (a+(b*c)) ((a+b)*(z+x)) ((a+t)*((b+(a+c))^(c+d))) Output: abc*+ ab+zx+* at+bac++cd+^* 知道其特点就好办: 1 遇到字母一定是可以输出的 2 遇到操作符号就入栈 3 遇到括号')',就出栈一个操作符 - 注意不是所有操作符出栈 不带括号的操作区别就在于是否需要判断符号的优先级. #include <stack> #i

将中缀式转化为逆波兰式 (栈)

逆波兰式:Reverse Polish notation,RPN,,也叫后缀表达式,将运算符写在操作数之后 数据结构:两个栈S1和S2.S1临时存储运算符,S2存储最终结果. 算法:(1)若取出的字符是操作数,则分析出完整的运算数,该操作数直接送入S2栈(2)若取出的字符是运算符,则将该运算符与S1栈栈顶元素比较,如果该运算符优先级大于S1栈栈顶运算符优先级,则将该运算符进S1栈,否则,将S1栈的栈顶运算符弹出,送入S2栈中,直至S1栈栈顶运算符低于(不包括等于)该运算符优先级,则将该运算符送入