递归:recursion,尾递归:tail recursion

What is tail recursion?

  • 普通递归会不断地累积占用栈空间,当到达一个峰值之后,再不断减小;

1. 从普通递归到尾递归

尾递归和一般的递归不同在对内存的占用,普通递归创建 stack 累积而后计算收缩,尾递归只会占用恒量的内存(和迭代一样)。SICP 中描述了一个内存占用曲线,以 Python 代码为例(普通递归),求解前 N 个自然数的和:

def recursum(N):
    if N == 1:
        return N
    return N + recursum(N-1)

如果在客户端调用recursum(5)函数,Python 解释器将会按如下形式执行:

recursum(5)
5 + recursum(4)
5 + (4 + recursum(3))
5 + (4 + (3 + recursum(2)))
5 + (4 + (3 + (2 + recursum(1))))
5 + (4 + (3 + (2 + 1)))
5 + (4 + (3 + 3))
5 + (4 + 6)
5 + 10
15

这个曲线就代表内存占用大小的峰值,从左到右,达到顶峰,再从右到左收缩。而我们通常不希望这样的事情发生(过于占用内存,但初值过大,递归调用层数够深时),所以使用迭代,只占据常量stack space(更新这个栈!而非扩展他)。

我们可以将该函数改造为尾递归版本:

def tail_recursum(n, running_total=0):
    if n == 0:
        return running_total
    return tail_recursum(n-1, running_total + n)

客户端调用如下:

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

2. 尾递归的含义

尾递归就是从最后开始计算,每递归一次就算出相应的结果,也就是说,函数调用出现在调用者函数的尾部,因为是尾部, 所以根本没有必要去保存任何局部变量. 直接让被调用的函数返回时越过调用者, 返回到调用者的调用者去。尾递归就是把当前的运算结果(或路径)放在参数里传给下层函数,深层函数所面对的不是越来越简单的问题,而是越来越复杂的问题,因为参数里带有前面若干步的运算路径。

尾递归是极其重要的,不用尾递归,函数的堆栈耗用难以估量,需要保存很多中间函数的堆栈。比如f(n, sum) = f(n-1) + value(n) + sum; 会保存n个函数调用堆栈,而使用尾递归f(n, sum) = f(n-1, sum+value(n)); 这样则只保留后一个函数堆栈即可,之前的可优化删去。

3. 将斐波那契改造为尾递归版

见识一下尾递归的强大!尾递归怎么会比迭代还快!这不科学

def tail_recur_fib(a, b, n):
    if n <= 1: return b
    return tail_recur_fib(b, a+b, n-1)
posted on 2016-09-18 15:29  未雨愁眸  阅读(482)  评论(0编辑  收藏  举报