一、遞歸
- 遞歸,是一種解決問題的方法,它解決問題的各個小部分,直到解決最初的大問題,遞歸通常涉及到函數的自身調用。
- 遞歸函數,能夠直接調用自身的方法或是函數。同樣的,間接的調用自己的函數也是遞歸函數。
- 每一個遞歸函數都必須要有邊界條件,即不再遞歸調用的條件(停止點),防止無限遞歸下去。如果忘記停止遞歸調用的邊界條件,遞歸調用並不無限的執行下去;瀏覽器會直接拋出錯誤,也就是我們所謂的棧溢出錯誤,每一個瀏覽器都有自己的上限。
- 遞歸,實現斐波那契函數,代碼如下所示:
function fibonacci(num){
if(num === 1 || num === 2){
return 1;
}
return fibonacci(num - 1) + fibonacci(num - 2);
}
二、波蘭式和逆波蘭式
- 波蘭式,在通常的表達式中,二元運算符總是置於與之相關的兩個運算對象之前,所以,這種表示法也稱爲前綴表達式。例如:
3*(2-(5+1))
,用波蘭式來表示是:* 3 - 2 + 5 1
。
- 閱讀這個表達式需要從左至右讀入表達式,如果一個操作符後面跟着兩個操作數時,則計算,然後將結果作爲操作數替換這個操作符和兩個操作數,重複此步驟,直至所有操作符處理完畢。從左往右依次讀取,直到遇到
+ 5 1
,做計算後,將表達式替換爲* 3 - 2 6
,然後再次從左往右讀取,直到遇到- 2 6
,做計算後,將表達式替換爲*3 (-4)
(這裏“-”
爲負號不是減號,-4
爲一個數負四),從而得到最終結果-12
。
- 逆波蘭式(
Reverse Polish notation,RPN
,或逆波蘭記法),也叫後綴表達式(將運算符寫在操作數之後)。例如:3*(2-(5+1))
,用逆波蘭式來表示是:3 2 5 1 + - *
,也就是把操作運算符往操作數後面放。
- 閱讀這個表達式需要從左往右讀入表達式,當讀到第一個操作符時,從左邊取出兩個操作數做計算,然後將這個結果作爲操作數替換這個操作符和兩個操作數,重複此步驟,直至所有操作符處理完畢。
- 逆波蘭式定義,將運算對象寫在前面,而把運算符號寫在後面。用這種表示法表示的表達式也稱做後綴式。逆波蘭式的特點在於運算對象順序不變,運算符號位置反映運算順序。
- 逆波蘭式的算法描述,根據普通算術表達式求逆波蘭式,如下所示:
- 首先構造一個運算符棧,此運算符在棧內遵循越往棧頂優先級越高的原則。
- 讀入一個用中綴表示的簡單算術表達式,爲方便起見,設該簡單算術表達式的右端多加上了優先級最低的特殊符號
“#”
。
- 從左至右掃描該算術表達式,從第一個字符開始判斷,如果該字符是數字,則分析到該數字串的結束並將該數字串直接輸出。
- 如果不是數字,該字符則是運算符,此時需比較優先關係。
做法如下:將該字符與運算符棧頂的運算符的優先關係相比較。如果,該字符優先關係高於此運算符棧頂的運算符,則將該運算符入棧。倘若不是的話,則將此運算符棧頂的運算符從棧中彈出,將該字符入棧。
- 重複上述操作,直至掃描完整個簡單算術表達式,確定所有字符都得到正確處理,我們便可以將中綴式表示的簡單算術表達式轉化爲逆波蘭表示的簡單算術表達式。
- 計算逆波蘭表達式的值,如下所示:
- 構造一個棧,存放運算對象。
- 讀入一個用逆波蘭式表示的簡單算術表達式。
- 自左至右掃描該簡單算術表達式並判斷該字符,如果該字符是運算對象,則將該字符入棧。若是運算符,如果此運算符是二目運算符,則將對棧頂部的兩個運算對象進行該運算,將運算結果入棧,並且將執行該運算的兩個運算對象從棧頂彈出。
- 重複上述操作直至掃描完整個簡單算術表達式的逆波蘭式,確定所有字符都得到正確處理,我們便可以求出該逆波蘭算術表達式的值。
- 逆波蘭式的代碼實現,如下所示:
(function () {
'use strict'
const rpn = {
_precedence: {'/': 2, '*': 2, '-': 1, '+': 1, '#': 0},
_operation: {
'+': (a, b) => (+a) + (+b),
'-': (a, b) => (+a) - (+b),
'*': (a, b) => (+a) * (+b),
'/': (a, b) => (+a) / (+b)
},
_splitExp: function (exp) {
return exp.match(/\d+|[^\d\s\t]/g);
},
_isOperator: function (char) {
return /^[\/\*\-\+#]$/.test(char);
},
_isBracket: function (char) {
return /^[\(\)]$/.test(char);
},
_isNumber: function (str) {
return /^\d+$/.test(str);
},
_isValidExpression: function (exp) {
return !/[^\d\s\t\+\-\*\/\(\)]/.test(exp);
},
infix2rpn: function(exp) {
if (!rpn._isValidExpression(exp)) return null;
var arrExp = rpn._splitExp(exp);
var opStack = [];
var rpnStack = [];
arrExp = arrExp.concat('#');
var i,
item,
op,
len = arrExp.length;
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];
if(op === '(') {
break;
} else if (rpn._precedence[item] > rpn._precedence[op]) {
break;
} else {
rpnStack.push(opStack.pop());
}
}
opStack.push(item);
} else {
if (item === '(') {
opStack.push(item);
} else {
while (opStack[opStack.length-1] !== '(') {
rpnStack.push(opStack.pop());
}
opStack.pop();
}
}
}
return rpnStack.length ? rpnStack.join(' ') : null;
},
rpnCalculate: function (exp) {
if (!rpn._isValidExpression(exp)) return null;
var arrExp = rpn._splitExp(exp);
var calcStack = [];
var item;
var param1, param2;
var i, len = arrExp.length;
for (i = 0; i < len; i ++) {
item = arrExp[i];
if (rpn._isNumber(item)) {
calcStack.push(+item);
} else {
param2 = calcStack.pop();
param1 = calcStack.pop();
calcStack.push(rpn._operation[item](param1, param2));
}
}
return calcStack.pop();
},
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;
}
}());