zoukankan      html  css  js  c++  java
  • python中对递归函数的理解

    在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

    举个例子,我们来计算阶乘n! = 1 * 2 * 3 * ... * n,用函数fact(n)表示,可以看出:

    fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n

    所以,fact(n)可以表示为n * fact(n-1),只有n=1时需要特殊处理。

    于是,fact(n)用递归的方式写出来就是:

    def fact(n):
        if n == 1:
            return 1
        return n * fact(n - 1)
    
    print(fact(5))

    递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

    使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就

    会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以

    试试fact(1000)

    它会报该错误:RecursionError: maximum recursion depth exceeded in comparison

    解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

    尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

    注意:不是所有语言都有尾递归优化的(Java就没有)

    def f(n):
        return fact_iter(n, 1)    # 返回的是另一个递归调用函数的结果
    
    def fact_iter(num, product):    # num是想要计算的值,product是结果
        if num == 1:
            return product
        return fact_iter(num - 1, num * product)    # 将乘积结果传入函数

    可以看到尾递归的函数比原始的版本多了1个参数,一个起到收集器(accumulator)的作用,记录每上一次栈的返回值,因为原来栈的空间会被下一层递归覆盖。

    尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

    遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。

    小结

    使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。

    针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。

    Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

  • 相关阅读:
    4.关于QT中的QFile文件操作,QBuffer,Label上加入QPixmap,QByteArray和QString之间的差别,QTextStream和QDataStream的差别,QT内存映射(
    PlSql加入数据库链接
    UserScan的处理流程分析
    第八十八题(金山笔试题,字符串移动)
    4Sum_leetCode
    LeetCode Add Binary
    Hibernate or JPA Annotation中BLOB、CLOB注解写法
    配置Nginx防止直接用IP訪问Webserver
    Java集合系列之TreeMap源代码分析
    使用Application Loader上传APP流程解读[APP公布]
  • 原文地址:https://www.cnblogs.com/mgw2168/p/9476374.html
Copyright © 2011-2022 走看看