算法之尾递归

来源:互联网 发布:云计算的特点有哪些 编辑:程序博客网 时间:2024/04/30 03:58
研一算法考试,老师出的题目就是尾递归,当场就懵逼了。。

什么是尾递归?就是函数返回之前的最后一个操作若是递归调用,则该函数进行了尾递归。先看一个例子:

例子1:阶乘

//阶乘递归实现int FactorialRecursion(int n){if(n==0) retrun 1;else retrun n*FactorialRecursion(n-1);}//尾递归实现int FactorialTailRecursion(int n, int acc){    if (n == 0) return acc;    return FactorialTailRecursion(n - 1, acc * n);}//循环实现int FactorialLoopOptimized(int n, int acc){    while (true)    {        if (n == 0) return acc;         acc *= n;        n--;    }}

阶乘的递归实现,函数返回之前的最后一个操作为乘法,不是递归调用,所以不是尾递归。

尾递归与循环:

我们可以发现,其实尾递归的过程和循环基本上是等价的,我们可以将尾递归的过程很方便到用循环来代替,所以很多的语言对尾递归提供了编译级别的优化,也就是将尾递归在编译期转化成循环的代码。不过对于没有提供尾递归优化的语言来说也是很有意义的,

由于递归在方法的末尾,因此方法中的局部变量已经毫无用处,编译器完全可以将其“复用”,并把尾递归优化为“循环”方式。所以尾递归的方式,类似循环。也可以说,尾递归可以转换为循环来实现。据网上查阅,java,C#和python都不支持编译环境自动优化尾递归,但是对于C语言来说,编译器白提供的服务,用了也不差,毕竟递归代码会好理解一点,但换句话说,如果写到尾递归这份上了,变成非递归已经很好实现了,完全可以用循环来搞定,所以呢,这个时候,就看个人喜好了。

例子2:斐波那契问题

//递归实现int fabonacci(int n)//复杂度为指数级{if(n==0) return 0;else if(n==1) return 1;else {return fabonacci(n-1)+fabonacci(n-2);}}//循环实现int fabonacci2(int n)//复杂度:O(n){int fab;int a=1,b=1;if(n==0) return 0;else if(n==1) return 1;else{for(int i=2;i<=n;i++){fab=a+b;a=b;b=fab;}}return fab;}//斐波那契尾递归实现int fabtrail(int n,int a,int b)//复杂度:O(n){if(n==0) return a;else if(n==1) return b;else return fabtrail(n-1,b,a+b);}


编译器是怎样优化尾递归的?

我们知道递归调用是通过栈来实现的,每调用一次函数,系统都将函数当前的变量、返回地址等信息保存为一个栈帧压入到栈中,那么一旦要处理的运算很大或者数据很多,有可能会导致很多函数调用或者很大的栈帧,这样不断的压栈,很容易导致栈的溢出。

我们回过头看一下尾递归的特性,函数在递归调用之前已经把所有的计算任务已经完毕了,他只要把得到的结果全交给子函数就可以了,无需保存什么,子函数其实可以不需要再去创建一个栈帧,直接把就着当前栈帧,把原先的数据覆盖即可。相对的,如果是普通的递归,函数在递归调用之前并没有完成全部计算,还需要调用递归函数完成后才能完成运算任务,比如return n * FactorialRecursion(n - 1);这句话,这个FactorialRecursion(n)在算完FactorialRecursion(n-1)之后才能得到n * FactorialRecursion n - 1)的运算结果然后才能返回。

综上所述,编译器对尾递归的优化实际上就是当他发现你在做尾递归的时候,就不会去不断创建新的栈帧,而是就着当前的栈帧不断的去覆盖,一来防止栈溢出,二来节省了调用函数时创建栈帧的开销,用《算法精解》里面的原话就是:“When a compiler detects a call that is tail recursive, it overwrites the current activation record instead of pushing a new one onto the stack.”



问题:

优化工作交给编译器还是交给自己?

我用java实现了斐波那契的尾递归,虽然算法复杂度从指数级降到了线性增长,但当n=100000时,就出现了stackOverFlowError,而在c中不会出现,说明java是没有对尾递归进行优化的,仍会出现堆栈溢出。

这个怎么说呢,据网上查阅,java,C#和python都不支持编译环境自动优化尾递归,这种情况下,当然是别用递归效率最高,可以看下这里http://www.cnblogs.com/Alexander-Lee/archive/2010/09/16/1827587.html(利用抛出异常,自己捕获,优化尾递归)。

注:http://www.cnblogs.com/JeffreyZhao/archive/2009/04/01/tail-recursion-explanation.html(老赵-尾递归优化)

这里提出了Continuation技术,不懂


0 0