zoukankan      html  css  js  c++  java
  • Python递归(recursion)专题

    我想把Python中的递归作为一个专题讨论一下。我在学习的时候,尝试使用“Python递归”作为关键词,在Google和百度中搜索,发现结果大部分是对某个具体例子的递归应用讨论,而对我这样的小白来说,切入点有点高。而我现在需要做的,是从基础概念开始。

    想到讨论递归问题,是因为那个著名的“字典序”问题,但还是先从最基本的递归概念开始。我希望我讨论完了这个,自己对递归也有一个基本的了解了。

    递归的概念很简单,如果函数包含了对其自身的调用,该函数就是递归的。拗口一点的定义是,如果一个新的调用能在相同过程中较早的调用结束之前开始,那么个该过程就是递归。两个定义都来自《Python核心编程第二版》的第304页。

    该书应用了一个经典的例子,来讲述递归的应用:

    阶乘函数的定义是:
    N! = factorial(N) = 1 * 2 * 3 * ... * N

    那么可以用这种方法来看阶乘函数:
    factorial(N) = N!
                 = N * (N - 1)!
                 = N * (N - 1) * (N - 2)!
                 = N * (N - 1) * (N - 2) * ... * 3 * 2 * 1
                 = N * factorial(N - 1)

    于是我们有了阶乘函数的递归版本:

    def factorial(n):
        if n == 0 or n == 1: return 1
        else: return (n * factorial(n - 1))

    print factorial(6)

    可以很轻易的得到,6!的结果是720。

    上面就是这本书关于递归的内容了,但关于Python的递归不仅仅就这么一点知识吧?

    来看看这个问题:

    还是这个函数factorial(N),让我们试试N = 999和N = 1000,问题来了,N = 999时能输出正确答案,但当N = 1000时,就出现下面的错误了:
    RuntimeError: maximum recursion depth exceeded
    于是,请记住,默认的Python有一个可用的递归深度的限制,以避免耗尽计算机中的内存。在我的电脑,这是1000。

    接着再来看看在我的第一个递归程序中遇到的问题:

    为了试试手,我打算写一个简单函数计算那个传统的问题,1 + 2 + 3 + ... + 100,当然,必须使用递归来做:

    我的脚本是这样的,结果出错了。

    >>> def add(n):
    ...     if n > 0: return n + add(n - 1)
    ...
    >>> add(100)
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
      File "<stdin>", line 2, in add
      File "<stdin>", line 2, in add
      ...
      File "<stdin>", line 2, in add
    TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

    上网问了之后,得知应该这样写这个脚本:
    >>> def add(n):
    ... if n <= 0: return 0 
    ... if n > 0: return n + add(n - 1)
    ...    
    >>> add(100)
    5050

    我本意想偷懒,在n小于等于零的时候,不返回任何东西。但Pyhton所做的和我想的却不一样。如果一个Python函数被设计成不返回任何东西,它会返回None。

    这里插入一些关于递归的网方解释,因为我是从网上搜到的这些内容:
    (1)递归就是在过程或函数里调用自身;
    (2)在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。

    递归算法一般用于解决三类问题:
    (1)数据的定义是按递归定义的。(比如Fibonacci函数)
    (2)问题解法按递归算法实现。(回溯)
    (3)数据的结构形式是按递归定义的。(比如树的遍历,图的搜索)   

    递归的缺点:递归算法解题的运行效率较低。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。

    【推荐】网文《精通递归程序设计》:
    http://www.ibm.com/developerworks/cn/linux/l-recurs.html

    递归程序的基本步骤,来自上面这篇文章

    每一个递归程序都遵循相同的基本步骤:
    1.初始化算法。递归程序通常需要一个开始时使用的种子值(seed value)。要完成此任务,可以向函数传递参数,或者提供一个入口函数,这个函数是非递归的,但可以为递归计算设置种子值。
    2.检查要处理的当前值是否已经与基线条件相匹配(base case)。如果匹配,则进行处理并返回值。
    3.使用更小的或更简单的子问题(或多个子问题)来重新定义答案。
    4.对子问题运行算法。
    5.将结果合并入答案的表达式。
    6.返回结果。

    基线条件(base case)。基线条件是递归程序的最底层位置,在此位置时没有必要再进行操作,可以直接返回一个结果。所有递归程序都必须至少拥有一个基线条件,而且必须确保它们最终会达到某个基线条件;否则,程序将永远运行下去,直到程序缺少内存或者栈空间。

    自己总结了一下,要写一个递归的程序,需要这样做:
    1.一个基线条件。请在递归函数的一开始就处理这个基线条件。
    2.一系列的规则,使对递归函数的每次调用都趋进于直至达到这个基线条件。

    好吧,现在看来,基本概念已经差不多了,在回到“字典序”问题之前,先看看这个全排列的问题。题目要求很简单,输入n个数,能自动打印出全排列(Permutation)。比如输入1,2,3,那它的全排列就是123,132,213,231,312,321。

    【参考】

    在《Pyhon核心编程》第二版第209页第八章练习 续 中,题8-6在函数findPrimeFactors中使用了递归,可以参考: http://www.cnblogs.com/balian/archive/2012/01/11/2318679.html

    【推荐】博客:CoderZh的技术博客
    http://www.cnblogs.com/coderzh/articles/1202040.html

  • 相关阅读:
    第六次作业--结对编程第二次
    第四次作业——项目选题报告
    第五次作业——结对
    第三次作业——团队项目展示
    第二次作业——个人项目实战
    第一次作业-准备
    码农开富农,锄头得先拿
    一个关于狗记录的Java练习
    一个随手练的题目后面再弄一个带面版的
    java拓荒者
  • 原文地址:https://www.cnblogs.com/balian/p/1951054.html
Copyright © 2011-2022 走看看