zoukankan      html  css  js  c++  java
  • 递归与尾递归

    递归: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)
  • 相关阅读:
    C# 数据库连接字符串拼接
    C# 线程同步计数存在的问题
    字符串操作
    字符串位置
    6个基本函数
    占位符
    str转换成int
    python运算符6
    python运算符5
    python运算符4
  • 原文地址:https://www.cnblogs.com/mtcnn/p/9423716.html
Copyright © 2011-2022 走看看