python递归基础

4.2 何谓递归


  递归是解决问题的一种方法,它将问题不断地分成更小的子问题,直到子问题可以用普通的方法解决。通常

情况下,递归会使用一个不停调用自己的函数。尽管表面上看起来很普通,但是递归可以帮助我们写出非常优雅

的解决方案。对于某些问题,如果不用递归,就很难解决。

4.2.1 计算一列数之和


  我们从一个简单的问题开始学习递归。即使不用递归,我们也知道如何解决这个问题。假设需要计算数字列

表[1, 3, 5, 7, 9]的和。代码清单4-1展示了如何通过循环函数来计算结果。这个函数使用初始值为0的累加变量

theSum,通过把列表中的数加到该变量中来计算所有数的和。

  代码清单4-1 循环求和函数

def listsum(numlist):
    thesum = 0
    for i in numlist:
        thesum = thesum + i
    
    return thesum


  假设暂时没有while循环和for循环。应该如何计算结果呢?如果你是数学家,就会记得加法是接受两个参数

(一对数)的函数。将问题从求一列数之和重新定义成求数字对之和,可以将数字列表重写成完全括号表达式,

例如((((1 + 3) + 5) + 7) + 9)。该表达式还有另一种添加括号的方式,即(1 + (3 + (5 + (7 + 9))))。注意,最内

层的括号对(7 + 9)不用循环或者其他特殊语法结构就能直接求解。事实上,可以使用下面的简化步骤来求总和。

    总和 = (1 + (3 + (5 + (7 + 9))))

    总和 = (1 + (3 + (5 + 16)))

    总和 = (1 + (3 + 21))

    总和 = (1 + 24)

    总和 = 25

  如何将上述想法转换成Python程序呢?让我们用Python列表来重新表述求和问题。数字列表numList的总

和等于列表中的第一个元素(numList[0])加上其余元素(numList[1:])之和。可以用函数的形式来表述这个

定义。返回列表中的第一个元素,则返回其余元素。用Python可以轻松地实现这个等式,如代码清单4-2所示。

  代码清单4-2 递归求和函数

def listsum(numlist):
    if len(numlist) == 1:

        return numlist[0]

    else:

        return numlist[0] + listsum(numlist[1:])


  在这一段代码中,有两个重要的思想值得探讨。首先,第2行检查列表是否只包含一个元素。这个检查非常

重要,同时也是该函数的退出语句。对于长度为1的列表,其元素之和就是列表中的数。其次,listsum函数在第

5行调用了自己!这就是我们将listsum称为递归函数的原因——递归函数会调用自己。

  图4-1展示了在求解[1, 3, 5, 7, 9]之和时的一系列递归调用。我们需要将这一系列调用看作一系列简化操作。

每一次递归调用都是在解决一个更小的问题,如此进行下去,直到问题本身不能再简化为止。


                图4-1 求和过程中的递归调用

  当问题无法再简化时,我们开始拼接所有子问题的答案,以此解决最初的问题。图4-2展示了listsum函数

在返回一系列调用的结果时进行的加法操作。当它返回到顶层时,就有了最终答案。



                图4-2 求和过程中的一系列返回操作


4.2.2 递归三原则

正如阿西莫夫提出的机器人三原则一样,所有的递归算法都要遵守三个重要的原则:

  (1) 递归算法必须有基本情况;

  (2) 递归算法必须改变其状态并向基本情况靠近;

  (3) 递归算法必须递归地调用自己。

  让我们来看看listsum算法是如何遵守上述原则的。基本情况是指使算法停止递归的条件,这通常是小到足

以直接解决的问题。listsum算法的基本情况就是列表的长度为1。

  为了遵守第二条原则,必须设法改变算法的状态,从而使其向基本情况靠近。改变状态是指修改算法所用

的某些数据,这通常意味着代表问题的数据以某种方式变得更小。listsum算法的主数据结构是一个列表,因此

必须改变该列表的状态。由于基本情况是列表的长度为1,因此向基本情况靠近的做法自然就是缩短列表。这正

是代码清单4-2的第5行所做的,即在一个更短的列表上调用listsum。

  最后一条原则是递归算法必须对自身进行调用,这正是递归的定义。对于很多新手程序员来说,递归是令

他们颇感困惑的概念。新手程序员知道如何将一个大问题分解成众多小问题,并通过编写函数来解决每一个小

问题。然而,递归似乎让他们落入怪圈:有一个需要用函数来解决的问题,但是这个函数通过调用自己来解决

问题。其实,递归的逻辑并不是循环,而是将问题分解成更小、更容易解决的子问题。

原文地址:https://www.cnblogs.com/mtfan01/p/14813610.html