前端跳槽面試總結之遞歸、波蘭式和逆波蘭式算法類

一、遞歸

  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;
  }
}());

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章