前言
说到递归,如果是从其他编程语言转到 Python 的童鞋对这个词一定不会陌生,在很多情况下,使用递归可以提高程序的可读性,虽然可以完全避免编写递归函数,转而使用循环来代替,但是作为程序猿,至少必须要能够读懂其他人编写的递归算法和函数吧。OK,废话不多说,来看一下 Python 中递归函数的写法。
定义
所谓递归,就是调用函数自身。
简单的说就是函数自己调用自己。递归可能难以理解,也可能非常简单,这取决于对它的熟悉程度。
下面是一个递归函数的定义:
>>> def recursion():
... return recursion()
这个递归定义显然什么都没有做,如果运行该函数的结果就是一段时间后程序就崩掉了。因为每次调用函数都将会消耗一些内存,当内存爆满就自然就挂了。
这个函数中的递归成为无穷递归,就好比一个 while 死循环,从理论上说它将永远不会结束,这显然不是我们想要的结果。
所以正常的递归函数通常包含以下两个部分;
1.基线条件(针对最小的问题):满足这种条件时函数将直接返回一个值。
2.递归条件:包含一个或多个调用,这些调用旨在解决问题的一部分。
这里的关键是,通过将问题分解为较小的部分,可避免递归没完没了,因为问题终将被分解成基线条件可以解决的最小问题。
OK,以上的定义和描述可能对于一个刚接触递归的人来说有些难以理解,接下来通过几个示例来看看递归的用法。
递归经典案例
计算阶乘
阶乘定义:n 的阶乘为 n*(n-1)*(n-2)*…*1
那么要计算阶乘,用传统的循环方式写法如下:
def factorial(n):
result = n
for i in range(1,n):
result *= i
return result
上述循环的思路大概是:首先将 result 设置成 n,然后通过循环,将 result 依次乘以1~(n-1)的每个数字,最后返回结果。
而通过递归的方式如何实现呢,再看阶乘的算法,n 的阶乘其实相当于 n 乘以(n-1)的阶乘,而1的阶乘为1。
通过以上分析,来看看通过递归来实现阶乘的写法:
def factorial(n):
if n == 1:
return 1
else:
return n*factorial(n-1)
很明显,通过递归来实现同样算法,代码非常简单,并且可读性也很好。这是前述定义的直接实现,只是别忘了函数调用factorial(n)和factorial(n-1) 是不同的实体。
计算幂
定义一个数字的整数次幂,有多种方式,先来看个简单的定义:power(x,n)(x 的 n 次幂)是将数字 x 自乘以 n-1次的结果,即将 n 个 x 相乘。
传统的写法,通过循环来实现:
def power(x,n):
result = 1
for i in range(n):
result *= x
return result
这是一个非常简单的小型函数,可将定义修改成递归的形式:
对于任何数字的0次幂都是1
当 n 大于0时,power(x,n)为 x和 power(x,n-1)的乘积。
那么来看看递归的写法:
def power(x,n):
if n == 0:
return 1
else:
return x*power(x,n-1)
二分法查找
二分法查找,这是一个非常经典的查找算法,所谓的二分法,就是让每次查找的范围减半,这样查找效率非常的高,比如说一个猜数游戏,从1~100数字中猜出对方想好的一个数字,如果从1到100一个个的猜,肯定能猜对,最多会猜100次,那么最少需要猜多少次呢,通过二分法实际上只需要7次就能猜出正确答案。
结合二分法的定义引出递归的定义和实现。
1.如果上限和下限相同,就说明它们都指向数字所在的位置,因此将该数字返回。
2.否则,找出区间的中间位置(上限和下限的平均值),再将数字确定是在左半部分还是有半部分,然后继续在数字所在的那部分中查找。
OK,接下来看看递归实现二分法算法:
def search(seq,number,lower = 0,upper = None):
if upper is None:
upper = len(seq) - 1
if lower == upper:
assert number == seq[upper]
return upper
else:
middle = (lower + upper) // 2
if number > seq[middle]:
return search(seq,number,middle + 1,upper)
else:
return search(seq,number,lower,middle)
这里将上限和下限值定义成可选,如果不指定上下限值,那么默认为序列的开头和结尾位置。该递归的实现完全由上面的定义一致。
来看看效果:
seq = [23,1,35,38,89,24,32,76]
seq.sort()
print(seq)
print(search(seq,32))
print(search(seq,89))
返回结果:
[1, 23, 24, 32, 35, 38, 76, 89]
3
7
二分法在对于一个非常大的序列中使用效率非常高,如果使用循环的方式一个个的去找,对于序列中元素较少的情况下还好,如果数据量非常大,查询效率就很低了。
由此可见,递归的写法非常简洁,合理使用递归程序的可读性将会大大的提高。