在正式开始前,先来回忆一个问题。
假定一对刚出生的小兔一个月后就能长成大兔,再过一个月便能剩下一对小兔,并且每个月都生一对小兔。一年以内没有发生死亡。那么有一对刚出生的兔子开始,12个月后会有多少对兔子呢?
以上来自小学六年级下册的数学课本。当时老师讲解的方案是找规律。
0月 | 1月 | 2月 | 3月 | 4月 | 5月 | 6月 | 7月 | 8月 | 9月 | 10月 | 11月 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 | 89 | 144 |
这也非常著名的斐波那契数列!
直到后来,我们使用数学公式来表达这个问题的解。
那现在我们对于这个问题,可以使用自己调自己的函数来解决,如下:
# 斐波那契数列
def fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)
如上,就是本文所要谈的话题,专业名词称为递归
。
光看上面的公式,不是很直观。
而放在数学学科中,常常以数形结合的方式,所以本文也效仿一下。
当我们想知道第n(n>2)
个月兔子的数量,就可以向下一层一层的向下去问,这个过程就叫做"递"。一直"递"到无法再"递"的节点,然后再将结果一层一层汇总,向上“归”。那么我们说这个过程,可以称之为递归。
再回到上文案例中,把问题形式和求解过程抽象出来
理论存在,那么来秒一道求阶乘问题!
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1)
print(factorial(5))
# 计算过程 5 * 4 * 3 * 2 * 1 = 120
谜底就在谜面上,其实只要写出递归,就已经写好了,因为你已经直到了如何拆分这个问题为递归模式。但是还没有完,往往我们递归的问题出在后面的异常超时等问题。
①当我们终止条件不正确的时候,见下图
如果上述阶乘的案例中,不小心将-
错写成了+
。那么势必会进入无法终止的条件,导致报错。
②除此之外,还有一种情况,即使你写好了终止条件,但是因为n
过大,导致循环次数过多,也会出现上述的情况,或者计算时间很长。拿常规的斐波那契写法举例(见前言中的案例),如果n设置为100,那么你将会看到控制台像卡住一样得不到结果(你需要进行大约 1.67 亿
次递归调用)。
对于①的情况,往往需要你重新设计算法,或debug检查变量的值(debug的方法见本文结尾)。
而情况②,则需要算法中常用的一种方法:剪枝。
仔细分析此案例中的递归,当n为5时,我们大概需要1次重复运算,就是f(3);而当n到6时,重复计算的次数来到了5次。
所以,如果我们将计算结果缓存起来,每次先去缓存里看有没有计算过,这样,我们像减去“递归”树的枝叶一样,节省了资源。具体代码如下:
from functools import lru_cache
import time
@lru_cache(maxsize=None)
def fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)
# 测试 运行时间
start = time.time()
print(fibonacci(100)) # 输出: 354224848179261915075
end = time.time()
print(end - start)
在这个例子中,我们使用了 functools
模块中的 lru_cache
装饰器来为 fibonacci
函数提供缓存。maxsize=None
表示缓存的大小没有限制。这将缓存所有已计算的斐波那契数,从而减少时间复杂度。
请注意,这种方法在计算大的斐波那契数时可能会消耗大量内存。你可以通过设置 maxsize
参数来限制缓存的大小。
递归只能来解数学题?上面两个案例中,我都是用来解决数学中的问题。不过只是为了省去其他学习门槛,接下来,看一看编程中实际的应用。
完整代码:
# 定义二叉树
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
# 测试数据
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
# 先序遍历
def preOrder(root):
if root is None:
return
print(root.data, end=' ')
preOrder(root.left)
preOrder(root.right)
# 执行
preOrder(root) # 输出:1 2 4 5 3
树的结构就是对递归最好的诠释。
可见下图看结果
如果自己一步一步的查看代码显然是不现实的,那么正确的debug递归代码,应该采取以下方案:
def factorial(n):
result = 1
for i in range(1, n+1):
result *= i
return result
print(factorial(10)) # 结果:3628800
import sys
sys.setrecursionlimit(50)
递归凭借着简洁的语法得到极大的关注,但是想要写好递归还是需要一定经验的。希望本文对你有所帮助。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
上火牙齿痛吃什么药 | 雾里看花是什么意思 | ur品牌属于什么档次 | 18年是什么年 | 09年的牛是什么命 |
大象吃什么食物 | 睡觉后脑勺出汗多是什么原因 | 为什么嗜睡 | 猪血炒什么好吃 | 贡眉是什么茶 |
灵芝长什么样子 | 玉米吃多了有什么坏处 | 肠炎吃什么好 | 豚鼠吃什么食物 | 什么是气虚 |
杯弓蛇影的寓意是什么 | 护照类型p是什么意思 | 人流后吃什么恢复快 | simon什么意思 | 肾虚和肾亏有什么区别 |
胃溃疡吃什么药好得快bfb118.com | 手指甲有黑色条纹是什么原因hcv9jop2ns2r.cn | 08年属什么生肖hcv9jop6ns1r.cn | 痔疮是什么症状hcv9jop5ns9r.cn | 细胞是什么hcv8jop6ns9r.cn |
腥臭味是什么妇科病hcv8jop7ns8r.cn | vk是什么hcv9jop3ns5r.cn | 脊髓病变是什么病hcv8jop4ns2r.cn | 淋巴细胞比率偏高是什么原因hcv9jop4ns9r.cn | 乌鸡汤放什么补气补血hcv8jop1ns6r.cn |
看腋下挂什么科liaochangning.com | 层峦叠翠的意思是什么hcv9jop2ns3r.cn | dbp是什么意思hcv9jop6ns4r.cn | 大人发烧吃什么药gangsutong.com | 排骨是什么肉hcv9jop3ns0r.cn |
总胆红素高是怎么回事有什么危害520myf.com | 琅玕是什么意思hcv8jop2ns8r.cn | 慢性胃炎吃什么药效果好hcv8jop8ns1r.cn | 浆水是什么hcv8jop9ns3r.cn | 食物中毒用什么药hcv9jop4ns5r.cn |