阶乘定义:n!=1×2×3×...×n。
阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n。
版本一:最简单版本
function factorial(num){
if(num<1){
return 1
}else{
return num * factorial(num-1)
}
}
版本二:和函数名解耦 复杂度o(n)
上面代码中函数的名字和函数的执行紧紧的耦合在一起,所以为了消除这种耦合, 就要使用argumnets的callee属性(指向函数)
function factorial(num){
if(num<1){
return 1
}else{
return num * arguments.callee(num-1)
}
}
版本三:ES6 尾递归 复杂度o(1)
阶乘的尾递归
function factorial(num,total=1){ //也可以通过柯里化传入默认值
if(num<=1){
return total
}
return arguments.callee(num-1,num*total)
}
factorial(5) // 120
较之前的递归比,ES6的尾递归多了一个参数total,表示存储的最后的结果,没看懂的可以试一下:
第一次 f(5,1)
return f(5-1,5*1),也就是f(4,5),所以此时num是4,total是5x1。
递归再去调用后return f(4-1,4x5x1),也就是f(3,4x(5x1)),此时num是3,total是4x5x1。
然后是f( 2, 3x (4x5x1) )
f( 1, 2x (3x4x5x1))
最后num<=1 return total 也就是 2x3x4x5x1 = 120
<!--普通方法一:-->
function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
}
function factorial(n) {
return tailFactorial(n, 1);
}
factorial(5) // 120
<!--方法二:柯里化-->
function currying(fn, n) {
return function (m) {
return fn.call(this, m, n);
};
}
function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
}
const factorial = currying(tailFactorial, 1);
factorial(5) // 120
四、这里普及一下几个知识点
1.什么是调用帧呢?
函数调用会形成一个调用帧。
内部函数B只有将结果返回外部函数A后,B的调用帧才会消失。
2.什么是尾调用?
函数的最后一步是调用另一函数,不一定在最后一行
function f(x) {
if (x > 0) {
return m(x) //是
}
return n(x); //也是
}
3. 什么是尾调用优化?
只保留内层函数的调用帧
因为 尾调用由于是函数的最后一步操作,所以不需要保留外层的函数的调用帧,因为调用位置,内部信息等都不会再用到,只要
直接用内层函数的调用帧,取代外层函数的调用帧就可以了。这也叫做尾调用优化。
4. 尾调用优化的意义?
如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项。这将大大节省内存,也就是尾调用优化的意义。
5.为什么尾递归的复杂度就是o(1)呢?
递归非常的消耗内存,因为需要同时保存成千上百个调用帧,很容易发生栈溢出的错误,但是对于尾递归来说,由于只存在一个调用帧,所以永远不会发生栈溢出。