一、递归
- 递归,是一种解决问题的方法,它解决问题的各个小部分,直到解决最初的大问题,递归通常涉及到函数的自身调用。
- 递归函数,能够直接调用自身的方法或是函数。同样的,间接的调用自己的函数也是递归函数。
- 每一个递归函数都必须要有边界条件,即不再递归调用的条件(停止点),防止无限递归下去。如果忘记停止递归调用的边界条件,递归调用并不无限的执行下去;浏览器会直接抛出错误,也就是我们所谓的栈溢出错误,每一个浏览器都有自己的上限。
- 递归,实现斐波那契函数,代码如下所示:
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;
}
}());