阶乘

阶乘的递归:

递归:

最常见的写法

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

  

posted @ 2020-05-14 19:53  Ms*Zhao*sM  阅读(1066)  评论(0编辑  收藏  举报