zoukankan      html  css  js  c++  java
  • 浅谈尾递归

    今天在围观大神博客时,看到尾递归这个名词,这里对自己看到的做个总结。

    1. 递归

     一个函数直接或间接的调用自身,这个函数就是一个递归函数。尾递归也是一种特殊的递归函数。

    如计算一个阶乘函数:

    public static int fac(int n)
    {
        if(n ==0 )
        {
            return 1;
        }
        if(n==1)
        {
            return 1;
        }
        else {
            return n*fac(n-1);
        }
    }

    这里fac(n)在计算过程中会不停的调用自身。

    递归函数的特点:

    (1)递归就是在过程或函数里调用自身。

    2)在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。(上面n==1,就是递归出口)

    递归函数在调用自身的过程中,需要将每一层函数的returnAddress,局部变量等保存在栈存储中进行后面的运算,所以当递归深度过大时,递归函数会出现栈溢出的错误。

    2. 尾递归

    尾递归是一种特殊的递归,满足的要求是:函数的最后执行代码除了调用函数自身外,不再执行其他运算。

    public static int facTail(int n,int m)
    {
        if(n==0)
        {
            return 1;
        }
        if(n==1)
        {
            return m;
        }
        else 
        {
            
            return facTail(n-1,m*n); 
        }
    }

    上面的函数就是一个尾递归函数 ,因为最后执行代码是调用函数自身。 

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

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

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

    目前编译器支持尾递归优化的语言有C,所以在其他语言下可以考虑将递归替换成迭代循环迭代来实现。 

    参考:

    1. 浅谈尾递归

    2. 递归与尾递归总结

      

  • 相关阅读:
    解决Android SDK Manager更新、下载速度慢
    selenium报错以及各解决方法
    webdriver对各种浏览器的支持
    selenium driver版本和Chrome浏览器版本对应关系
    selenium web driver
    js中变量注意事项
    js选项卡实现
    两种JS方法实现斐波那契数列
    三种JS方法确定元素在数组中的索引值
    javascript内置属性——arguments
  • 原文地址:https://www.cnblogs.com/limingluzhu/p/5606488.html
Copyright © 2011-2022 走看看