尾递归(tail recursion) 的简单使用

摘要

       本文首先论述了尾递归的定义,然后通过实例进行讲解尾递归的本质,最后给出了两个例子完整代码。
引导:
         什么是递归?通俗的说,就是在一个函数中不断调用自己的这么一种调用形式。递归函数的设计,就是截止条件+调用本身。大家都知道,递归的资源消耗是发生在栈中,在调用函数时候,通常需要保存相应的资源,比如参数,返回地址,局部变量等。那么进行大量的递归,就会造成栈的溢出。windows下栈为 1M。那么是什么导致了溢出呢?就是在进行递归的时候,本身递归函数的状态,没有完全执行完毕,需要借助于下一个状态来完成,那么上一个状态必然要进行保存。这样就导致了大量的中间过程。才肯能在回溯递归的时候,一一使用,最后拼凑出原来的结果。
比如阶乘函数:
int factorial(int n)
{
	if(n==0)
		return 1;
	else 
		return n*factorial(n-1);
}
        在上述的函数中,返回的值并不是一个完全独立的状态,而需要保存n这样一种额外需维护的变量,每层递归都需要这样一个变量,那么在下次调用的时候,就会重新创建新的栈帧来进行保存。每次都是这样。所以对于大数而言,就可能溢出了。那么仔细观察这种递归的本质,就是因为创建了很多中间过程需要存储,才导致了这样一种空间的浪费。那么我们在设计递归函数的时候,能不能直接省略中间变量,直接返回结果呢?下面就是尾递归的设计思路了。
什么是尾递归:
      本质就是不产生出中间过程,将中间过程作为函数的参数,传入到下次调用中去。比如上面阶乘函数,使用尾递归设计后。
int factorial_tailcursion(int n,int acc)
{
	if(n==0)
		return acc;
	else 
		return factorial_tailcursion(n-1,acc*n);
}
      在调用的时候 factorial_tailcursion(n,1) 需要一个初始累计器acc=1。可以看出,在每次调用完毕尾递归形式的阶乘函数后,根本没有产生人中间过程,中间变量都通过参数传递给下一次调用。从而节省了栈的开销。因为对于某些编译器而言,会自动识别到这一动作,并且认为每次调用过程都是独立的,那么就不需要每次都开辟新的 栈帧来保存当前值,而是直接就地覆盖调之前的栈。那么其空间复杂度将为O(1)。这就是尾递归的本质。说白了,也就是在进行递归设计的时候,尽量省略中间过程。
下面是两个例子的完整代码:
/***********************************************************************
two examples of tail recursion 
1. factorial 
2. fibonacci
*********************************************************************/
#include <iostream>
using namespace std;
int factorial(int n) // usual recursion call
{
	if(n==0)
		return 1;
	else 
		return n*factorial(n-1);
}

int factorial_tailrecursion(int n,int acc) // tail recursion call
{
	if(n==0)
		return acc;
	else 
		return factorial_tailrecursion(n-1,acc*n);
}

int fibinacci(int n) 
{
	if(n<2)
		return n;
	else
		return fibinacci(n-1)+fibinacci(n-2);
}
int  fibinacci_tailrecursion(int n,int acc1,int acc2)
{
	if(n==0)
		return acc1;
	else 
		return fibinacci_tailrecursion(n-1,acc1+acc2,acc1);
}
int main()
{	
	puts("usual factor recursion 4!");
	cout<<factorial(4)<<endl;
	puts("tail recursion 4!");
	cout<<factorial_tailrecursion(4,1)<<endl;
	puts("usual fibinacc ");
	for(int i=0;i<10;i++)
		cout<<fibinacci(i)<<" ";
	cout<<endl;
	puts("tail recursion ");
	for(int i=0;i<10;i++)
		cout<<fibinacci_tailrecursion(i,0,1)<<" ";
	return 0;
}


小结:

         递归函数的使用,无疑对一些复杂的问题,可以清晰的反应其本质,使得易于理解。everything is banlance!有利必有弊,我们必须尽量减少这中弊。

   更详细的参考:老赵这篇文章
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值