【python】19_函数之递归函数

【摘要】本博文通过三个例子来讲述递归函数的使用,增强读者对递归函数的理解与使用。

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之内的斐波那契数列

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值