前端跳槽面试总结之递归、波兰式和逆波兰式算法类

一、递归
  1. 递归,是一种解决问题的方法,它解决问题的各个小部分,直到解决最初的大问题,递归通常涉及到函数的自身调用。
  2. 递归函数,能够直接调用自身的方法或是函数。同样的,间接的调用自己的函数也是递归函数。
  3. 每一个递归函数都必须要有边界条件,即不再递归调用的条件(停止点),防止无限递归下去。如果忘记停止递归调用的边界条件,递归调用并不无限的执行下去;浏览器会直接抛出错误,也就是我们所谓的栈溢出错误,每一个浏览器都有自己的上限。
  4. 递归,实现斐波那契函数,代码如下所示:
function fibonacci(num){
  if(num === 1 || num === 2){
    return 1;
  }
  return fibonacci(num - 1) + fibonacci(num - 2);
}
二、波兰式和逆波兰式
  1. 波兰式,在通常的表达式中,二元运算符总是置于与之相关的两个运算对象之前,所以,这种表示法也称为前缀表达式。例如:3*(2-(5+1)),用波兰式来表示是:* 3 - 2 + 5 1
  2. 阅读这个表达式需要从左至右读入表达式,如果一个操作符后面跟着两个操作数时,则计算,然后将结果作为操作数替换这个操作符和两个操作数,重复此步骤,直至所有操作符处理完毕。从左往右依次读取,直到遇到+ 5 1,做计算后,将表达式替换为* 3 - 2 6,然后再次从左往右读取,直到遇到- 2 6,做计算后,将表达式替换为*3 (-4)(这里“-”为负号不是减号,-4为一个数负四),从而得到最终结果-12
  3. 逆波兰式(Reverse Polish notation,RPN,或逆波兰记法),也叫后缀表达式(将运算符写在操作数之后)。例如:3*(2-(5+1)),用逆波兰式来表示是:3 2 5 1 + - *,也就是把操作运算符往操作数后面放。
  4. 阅读这个表达式需要从左往右读入表达式,当读到第一个操作符时,从左边取出两个操作数做计算,然后将这个结果作为操作数替换这个操作符和两个操作数,重复此步骤,直至所有操作符处理完毕。
  5. 逆波兰式定义,将运算对象写在前面,而把运算符号写在后面。用这种表示法表示的表达式也称做后缀式。逆波兰式的特点在于运算对象顺序不变,运算符号位置反映运算顺序。
  6. 逆波兰式的算法描述,根据普通算术表达式求逆波兰式,如下所示:
  • 首先构造一个运算符栈,此运算符在栈内遵循越往栈顶优先级越高的原则。
  • 读入一个用中缀表示的简单算术表达式,为方便起见,设该简单算术表达式的右端多加上了优先级最低的特殊符号“#”
  • 从左至右扫描该算术表达式,从第一个字符开始判断,如果该字符是数字,则分析到该数字串的结束并将该数字串直接输出。
  • 如果不是数字,该字符则是运算符,此时需比较优先关系。
    做法如下:将该字符与运算符栈顶的运算符的优先关系相比较。如果,该字符优先关系高于此运算符栈顶的运算符,则将该运算符入栈。倘若不是的话,则将此运算符栈顶的运算符从栈中弹出,将该字符入栈。
  • 重复上述操作,直至扫描完整个简单算术表达式,确定所有字符都得到正确处理,我们便可以将中缀式表示的简单算术表达式转化为逆波兰表示的简单算术表达式。
  1. 计算逆波兰表达式的值,如下所示:
  • 构造一个栈,存放运算对象。
  • 读入一个用逆波兰式表示的简单算术表达式。
  • 自左至右扫描该简单算术表达式并判断该字符,如果该字符是运算对象,则将该字符入栈。若是运算符,如果此运算符是二目运算符,则将对栈顶部的两个运算对象进行该运算,将运算结果入栈,并且将执行该运算的两个运算对象从栈顶弹出。
  • 重复上述操作直至扫描完整个简单算术表达式的逆波兰式,确定所有字符都得到正确处理,我们便可以求出该逆波兰算术表达式的值。
  1. 逆波兰式的代码实现,如下所示:
// 适用于无符整数四则运算, 但运算结果可能是负数,如减法
(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;
  }
}());

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值