Python笔记之递归函数

递归函数

Python支持递归函数——即直接或者间接地调用自身以进行循环的函数。递归是Python中比较的高级的话题,并且它在Python中比较少见。然后,它是一项非常有用的技术,因为它允许程序遍历拥有任意的,不可预知的形状的结构。

用递归求和

我们来看一个例子。假如要对一个数字列表求和,我们可以使用内置的sum函数,或者是自己编写一个更加定制化的版本。示例1是用递归编写的一个定制求和函数:

#示例1
>>> def mysum(N):
...     if not N:
...             return 0
...     else:
...             return N[0] + mysum(N[1:])
...
>>> mysum(range(10))  #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
45

在每一层。这个函数都递归地调用自己来计算列表剩余的值的和,这个和随后加到前面的一项中。当列表为空的时候,递归循环结束并返回0.就像这样使用递归的时候,对函数调用的每一个打开的层级,在运行时调用堆栈上都有自己的一个函数本地作用域的副本,也就是说,这意味着N在每个层级都是不同的。

这可能对新手比较难以理解,我们可以这样来看示例2。尝试给函数添加一个N的打印并再次运行它,从而在每个调用层级记录下当前列表:

示例2
>>> def mysum(N):
...     print(N)
...     if not N:
...             return 0
...     else:
...             return N[0] + mysum(N[1:])
...
>>> mysum(list(range(10)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[2, 3, 4, 5, 6, 7, 8, 9]
[3, 4, 5, 6, 7, 8, 9]
[4, 5, 6, 7, 8, 9]
[5, 6, 7, 8, 9]
[6, 7, 8, 9]
[7, 8, 9]
[8, 9]
[9]
[]
45

正如你所看见的,在每个递归的层级上,要加和的列表变得越来越小,直到它变为空——递归循环结束。加和随着递归调用的展开而计算开来。

编码替代方案

这有一个很有意思的是,我们可以使用Python的三元if/else表达式在这里保存某些代码。我们也可以针对任何可加和的类型一般化,

以下是一些例子:

示例3-1
>>> def mysum(N):
...     return 0 if not N else N[0] + mysum(N[1:])
...
>>> mysum(list(range(10)))
45

示例3-2
>>> def mysum(N):
...     return N[0] if len(N) == 1 else N[0] + mysum(N[1:])
...
>>> mysum(list(range(10)))
45


示例3-3
>>> def mysum(N):
...     first, *rest = N
...     return first if not rest else first + mysum(rest)
...
>>> mysum(list(range(10)))
45

#示例3-2和3-3会由于空的列表而失败。

示例3-2和3-3会由于空的列表而失败,但考虑到支持+任何对象类型的序列,而不单单是数字:

>>> mysum([1])
1
>>> mysum(list(range(10)))
45
>>> >>> mysum(('m', 'y', 'l', 'o', 'v', 'e', 'r'))
'mylover'
>>> mysum(['my', 'sweet', 'heart'])
'mysweetheart'

如果你比较好奇去研究这3个变体,将会发现,后2个在一个单个字符串参数上也有效(例如:mysum('love')),因为字符串是一字符的字符串的序列; 第三个变体中任意可迭代对象上都有效(包括打开的文件)。

前面我们说过递归可以是直接的,就像目前所给出的例子一样,也可以是间接的,就像下面将要给出的例子一样(一个函数调用另一个函数,后者反过来调用其调用者)。直接的效果是相同的,尽管这在每个层级有2个函数调用:

#示例4
>>> def mysum(N):
...     if not N:
...         return 0
...     return nonempty(N)
...
>>> def nonempty(N):
...     return N[0] + mysum(N[1:])
...
>>> mysum(list(range(10)))
45

循环语句OR递归

尽管递归对于之前求和的例子都有效,但是在那种环境中,它可能有点过于追求技巧了。实际上,递归在Python中并没有像Lisp那些语言中那样常用,因为Python强调像循环这样的简单的过程式语句,循环语句通常更为自然。例如,while常常使得事情变得更为具体一些,并且它不需要定义一个支持递归调用的函数:

#示例5
>>> L = list(range(10))
>>> sum = 0
>>> while L:
...     sum += L[0]
...     L = L[1:]
...
>>> sum
45

更好的情况是for循环为我们自动迭代,使得递归在大多数情况下不必使用(有可能,递归在内存空间和执行时间方面效率比较低):

>>> L = list(range(10))
>>> sum = 0
>>> for x in L:
...     sum += x
...
>>> sum
45

处理任意结构

另一方面,递归可以要求遍历任意形状的结构(我们前面说过)。作为递归在这种环境中的应用的一个简单的例子,考虑像下面这样的一个任务:

#示例6
#计算一个嵌套的子列表结构中的所有数字的总和
>>> def sumtree(N):
...     tot = 0
...     for x in N:
...         if not isinstance(x, list):
...             tot += x
...         else:
...             tot += sumtree(x)
...     return tot
...
>>> N = [2, [2, 5, [6, 3, [9]] , 6], 6, [5, 1]]
>>> print(sumtree(N))
45

简单的循环语句在这里不起作用了,因为这不是一个线性迭代。嵌套的循环语句也不够用,因为子列表可能嵌套到任意的深度并且以任意的形式嵌套。相反,示例6的代码却能够使用递归来对应这种一般性的嵌套,以便顺序访问子列表。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值