美文网首页
详解什么是尾递归(javascript版本)

详解什么是尾递归(javascript版本)

作者: sphenginx | 来源:发表于2020-04-30 16:39 被阅读0次

    在传统的递归中,典型的模型是首先执行递归调用,然后获取递归调用的返回值并计算结果。以这种方式,在每次递归调用返回之前,您不会得到计算结果。传统地递归过程就是函数调用,涉及返回地址、函数参数、寄存器值等压栈(在x86-64上通常用寄存器保存函数参数),这样做的缺点有二:

    • 效率低,占内存
    • 如果递归链过长,可能会statck overflow

    尾递归的原理:

    当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高

    在尾递归中,首先执行计算,然后执行递归调用,将当前递归计算的结果传递给下一个递归。这导致最后一个语句采用的形式return (recursive-function params) 。基本上,任何给定递归步骤的返回值与下一个递归调用的返回值相同。

    我们考虑一个最基本的关于N的求和函数,(例如sum(5) = 1 + 2 + 3 + 4 + 5 = 15)。

    这是一个使用JavaScript实现的递归函数:

    function calcSum(x) {
        if (x===1) {
            return x;
        } else {
            return x + recsum(x-1);
        }
    }
    

    如果你调用calcSum(5),JavaScript解释器将会按照下面的次序来计算:

    calcSum(5)
    5 + calcSum(4)
    5 + (4 + calcSum(3))
    5 + (4 + (3 + calcSum(2)))
    5 + (4 + (3 + (2 + calcSum(1))))
    5 + (4 + (3 + (2 + 1)))
    15
    

    注意在JavaScript解释器计算calcSum(5)之前,每个递归调用必须全部完成。

    这是同一函数的尾递归版本:

    function tailSum(num, total = 0) {
        if (num ===0 ) {
            return total ;
        } else {
            return tailSum(num -1,  total +num);
        }
    }
    

    下面是当你调用tailSum(5)的时候实际的事件调用顺序:

    tailSum(5, 0)
    tailSum(4, 5)
    tailSum(3, 9)
    tailSum(2, 12)
    tailSum(1, 14)
    tailSum(0, 15)
    15
    

    在尾递归的情况下,每次递归调用的时候,total 都会更新。

    如此, 每次执行完递归, 占用内存的只有一个方法,可以有效的避免因为太多调用而导致的内存泄露

    相关文章

      网友评论

          本文标题:详解什么是尾递归(javascript版本)

          本文链接:https://www.haomeiwen.com/subject/ptctghtx.html