【摘要】本博文通过三个例子来讲述递归函数的使用,增强读者对递归函数的理解与使用。
1. 何为递归函数
我们知道,在函数内部可以调用其他的函数。那么,可以调用自己吗?当然是可以的。
如果在函数内部调用函数本身,那么这个函数就是递归函数。下面我们就用三个例子来讲解递归函数的使用。
2.阶乘的递归实现
阶乘的计算规则如下:
0! = 1
1! = 1
2! = 2 * 1 = 2 * 1!
3! = 3 * 2 * 1 = 3 * 2!
4! = 4 * 3 * 2 * 1 = 4 * 3!
…
n! = n * n-1 *n-2 …1 = n * (n-1)!
通过观察,我们总结出来n!就等于n*(n-1)!
常规方法实现阶乘的代码如下:
def factorial(num):
"""
求num的阶乘
"""
result = 1
for item in range(1, num + 1):
result = result * item
return result
很容易理解,从1一直循环到num,再用result存储这些循环的值相乘的结果。
print("3的阶乘: ", factorial(3))
print("6的阶乘: ", factorial(6))
但我们知道,阶乘的规律是n!=n*(n-1)!
例如我们求3的阶乘, 3! = 3 * 2 * 1 = 3 * 2!,再对2求一次阶乘2! = 2 * 1 = 2 * 1!,再对1求一次阶乘 1! = 1,至此就结束了。相当于我们在函数内部调用自己不断去求n-1的阶乘、n-2的阶乘直到求到1的阶乘为1后,再一步步将结果带回上一层函数。
代码的实现如下:
def recursive_factorial(num):
"""
使用递归求num的阶乘
"""
# 如果num等于0或者1, 返回1;
if num in (0, 1):
return 1
# num的阶乘为num*(num-1)!
else:
return num * recursive_factorial(num - 1)
print("3的阶乘: ", recursive_factorial(3))
print("6的阶乘: ", recursive_factorial(6))
【补充】阶乘的递归实现虽然简单易懂,但是其调用深度是有限制的,我的笔记本只能递归到998层,到999层就栈溢出了。
print("998的阶乘: ", recursive_factorial(998))
这个数字很大,没有截全部。
print("998的阶乘: ", recursive_factorial(999))
尝试999的阶乘递归时,会报如下错误:
所以这里对阶乘的调用,注意深度不是无限的。
3.汉诺塔问题
这个问题相信学过谭浩强C语言的读者一定不陌生,当时觉得很绕,现在我们再用python实现回顾一下,这里我再对问题进行描述一下。
印度的古老传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。
这里我们看一下三个盘子的移动过程:
通过图解,我们发现三个盘子的移动需要7步,简单易懂。同理,64个盘子也是这样实现,但是步骤非常复杂,人脑根本无法想到如何移动。其实就算是10个盘子,5个盘子的移动,也是非常消耗脑力。那么我们可以考虑通过代码,让计算机帮我们计算出盘子的移动步骤。
如何实现呢?算法思路如下:
对于问题N,如果N-1已经解决了,那么N是否很容易解决?
如果要解决问题N-1,先解决问题N-2,那么N-1就更容易实现。
…
直到解决到剩3个盘子、2个盘子、到最后剩1个盘子即只需要一步。
代码实现如下:
moveCount = 0
def hanoi(n, a='A', b='B', c='C'):
"""
:param n:盘子个数
:param a:初始塔
:param b:缓存塔
:param c:目标塔
:return:移动次数
"""
if n == 1:
global moveCount
moveCount += 1
print(a, "--->", c)
else:
# 只是调用自己, 并没有真实的移动;
# 1). 打开冰箱门
hanoi(n - 1, a, c, b)
# 2). 把大象放进去
hanoi(1, a, b, c)
# 3). 关闭冰箱门
hanoi(n - 1, b, a, c)
hanoi(4)
print("移动次数:", moveCount)
这里通过Debug调试一下,走一下流程对代码也就有更加清晰的理解了。
函数的入口是
通过调用上面我们定义的函数,我们给n传入4表示有4个盘子需要移动,其他三个默认参数没有传值,表示在盘子移动的伊始,变量a(初始塔)的值是‘A’,变量b(缓冲塔)的值是‘B’,变量c(目标塔)的值是‘C’
n=4传入到函数内部,直接执行else语句的第一行代码,即对n=4-1=3再进行调用hanoi函数。这一步骤对应我们上面的第二、三幅图–>打开冰箱门。此时将a='A’传给形参a,表示初始塔的值是A;将c=‘C’传给形参b,表示缓冲塔的值是C;将b=‘B’传给形参c,表示目标塔的值是B。读者可以参考上面第三幅图理解,即将除最大盘子以外的其他盘子都从A塔挪到B塔。
此时再进行递归,将n=3、n=2、n=1传入函数内部。
当把冰箱门一层一层打开之后,只剩下最大的盘子,我们执行
参照第四幅图。
参照第五幅图。
读者可以自行调试代码,理解一下。4个盘子的移动顺序如下图所示:
通过测试,我们发现汉诺塔盘子的个数n和移动次数moveCount之间的数学等式为:
n==1, moveCount = 1 = (2^1-1)次
n==2, moveCount = 3 = (2^2-1) 次
n==3, moveCount = 7 = (2^3-1) 次
…
n== n, moveCount = 2^n-1
4.斐波那契数列
这个数列就是谭浩强C语言书上的小兔子生兔子的问题
问题描述如下:
斐波那契数列(Fibonacci
sequence),又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1,
F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
这个数列的规律如上所示,F(1)=1,F(2)=1…F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
算法思路和阶乘类似,遇到n=2或者1的情况,为递归出口将返回值一步一步带回上一层即可。
def fib(num):
"""
斐波那契数列
:param num:
:return:
"""
if num <= 2:
return 1
else:
return fib(num - 1) + fib(num - 2)
num = int(input('input num:'))
for i in range(1,num+1):
print(fib(i),end=' ')
在python中还有一个特有的方法实现斐波那契数列,非递归。
def fib(n):
a = 0
b = 1
while a <= n:
print(a, end=" ")
a, b = b, a + b # python不借助变量交换两数的值
这里要注意的是最后一句代码,b和a+b被封装成元组,将这个元组整体赋值给(a,b)
这是python特有的语法,无需借助临时变量,注意体会与c语言的不同。
fib(10000) # 求n之内的斐波那契数列