递归是指函数在定义中调用函数自身的方式,是数学归纳法思维的编程体现。
是不是有点晕,来看例1:
在上式对阶乘的定义中,计算n!需要知道(n-1)!,计算(n-1)!需要知道(n-2)!......以此类推,一直到计算2!需要知道1!,而从上面的定义中可知1!=1。那么,2!就可以算出来了,3!, 4! ......这样反向进行运算,最后就可以得到n!了。
从这个例子可以看出,递归有两个关键特征:
- 链条:计算过程存在链条,例1中的链条是n! = n(n-1)!
- 基例:存在一个或多个不需要再次递归的基例,例1的基例是1! = 1
def fact(n):
if n == 0:
return 1 # 基例
else:
rturn n*fact(n-1) # 链条
从这段简单的代码可以得到递归实现的一般套路:函数+分支语句
- 递归本身是个函数,需要通过函数定义方式描述
- 在函数内部,采用分支语句对输入参数进行判断,针对基例和链条分别编写对应代码
在上述代码中,5!的阶乘是怎么实现的呢?递归先重复的复制自身并不断代入更小的参数,就是下图蓝色箭头所示;当输入参数下降到基例n=0时,将基例的值代入再不断往前推最终得到n=5的阶乘,如下图红色箭头所示。
再来看看其它几个例子:
# 例2:字符串反转( 不准用s[::-1]哈哈哈)
def rvs(s):
if s == '':
return s # 基例
else:
return rvs(s[1:]) + s[0] # 链条
# 例3:斐波那契数列
def f(n):
if n == 1 or n == 2:
return 1
else:
return f(n - 1) + f(n - 2)
for i in range(1, 31):
print(f(i))
# 例4:汉诺塔问题
# 问题描述:
# 将一摞从小到大堆放的圆盘从src(source)杆移动到dst(destination)杆
# 可以利用mid(middle)杆,一次移动一个,小的在上面
count = 0 # 移动次数
def hanoi(n, src, dst, mid): # n 圆盘数量;src 原杆; dst 目标杆; mid 中间杆
global count # 全局变量声明
if n == 1:
print('{}:{}->{}'.format(n, src, dst))
count += 1
else: # 递归链条,调用了两次自身函数
hanoi(n-1, src, mid, dst) # 把n-1个从src移动到mid
print('{}:{}->{}'.format(n, src, dst)) # 把第n个圆盘从src移动到dst
count += 1
hanoi(n-1, mid, dst, src) # 把mid上的n-1个圆盘移动到dst