阶乘
阶乘的递归:
递归:
最常见的写法
function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } factorial(5) // 120
上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。
函数调用自身,称为递归,递归十分耗费内存,因为需要同时保存成千上百个调用记录,所以很容易发生“栈溢出(stack overflow)”错误.
我们知道,函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用记录上方,还会形成一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。如果函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。所有的调用记录,就形成一个"调用栈"(call stack)
尾调用优化:
1.什么是尾递归?
答:某个函数的最后一步是调用另一个函数。
例子:
function f(x){ return g(x); } //函数f最后一步是调用函数g,这就是尾调用
function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}
//这种m和n都属于尾调用,因为他们都是函数f的最后一步操作
反例:
// 情况一 function f(x){ let y = g(x); return y; } // 情况二 function f(x){ return g(x) + 1; } //以上两种都不是尾调用,因为还有别的操作。
2.尾调用优化的优点
尾调用之所以与其他调用不同,就在于它的特殊的调用位置。
尾调用优化即只保留内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是"尾调用优化"的意义。
上面的阶乘递归改写成尾递归的话,只需保留一个调用记录,复杂度为o(1)
function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5, 1) // 120
3.尾递归的改写
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。比如上面的例子,阶乘函数 factorial 需要用到一个中间变量 total ,那就把这个中间变量改写成函数的参数。这样做的缺点就是不太直观,第一眼很难看出来,为什么计算5的阶乘,需要传入两个参数5和1?
两个方法可以解决这个问题。方法一是在尾递归函数之外,再提供一个正常形式的函数。
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 //上面代码通过一个正常形式的阶乘函数 factorial ,调用尾递归函数 tailFactorial ,看起来就正常多了。
第二种方法就简单多了,就是采用ES6的函数默认值。
function factorial(n, total = 1) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5) // 120 //上面代码中,参数 total 有默认值1,所以调用时不用提供这个值。
4.尾调用总结
递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持"尾调用优化"的语言(比如Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。
摘自:http://www.ruanyifeng.com/blog/2015/04/tail-call.html
(ps:这篇博文太赞)
5.算法第二次作业,打印n的阶乘计算的过程:
//尾递归的写法: #include<iostream> using namespace std; int f(int n,int total){ if (n==1) return total; return f(n-1,n*total); } int main(){ int n; int total; int k=1; cout<<"请输入:"<<endl; cin>>n; f(n,total); cout<<"f("<<n<<","<<"1)"<<endl; for(int i=n-1;i>=1;i--) { k=(i+1)*k; cout<<"f("<<i<<","<<k<<")"<<endl; } cout<<"最后结果="<<f(n,1); }
//普通调用过程
#include<iostream> using namespace std; int F(int n){ if(n==1) return 1; return n*F(n-1); } int main(){ int n; cout<<"请输入你要计算的数字阶乘:"<<endl; cin>>n; //打印调用的过程 for(int i=n;i>1;i--) { cout<<"f("<<i<<")="<<i<<"*f("<<i-1<<")"<<endl; } cout<<"f(1)=1"<<endl; cout<<"最后结果="<<F(n); return 0; }