Recursion(递归)
让我们从一个例子开始。以洋葱为例,因为你可能会认为每个洋葱的发育都不一样。它们大小不同,外层和内层的数量也不同。所以每次我们剥一个新的洋葱,从表层到核心的工作都是不同的。如果我们把剥掉洋葱外层的操作看作一次操作,那么一个洋葱可能需要10次剥皮操作。另一个可能需要15个。而另一个可能只需要5个。
所以,给我们一篮子洋葱去皮,你可以想象,如果我们用手去剥每一个洋葱,那将是一个很大的工作!
但是,也许我们可以将工作自动化,从而节省一些时间、精力和不可避免的洋葱眼泪……
假设洋葱是一个Python对象,我们可以编写一些代码来剥离它。
import random
class Onion:
layers = 0
def __init__(self):
self.layers = random.randrange(5, 15)
def removelayer(self):
if self.layers > 0:
self.layers -= 1
def is_layer(self) -> bool:
return self.layers > 0
countlayers()函数看起来是一个很好的方法去剥离我们的洋葱对象的层,对吗?事实上,它是!虽然洋葱并不是递归属性的完美隐喻,但它为我们提供了一个可以构建的受约束的概念模型。
def countlayers():
onions = Onion(), Onion(), Onion()
layers = 0
for onion in onions:
while onion.is_layer():
onion.removelayer()
layers += 1
print(layers)
好了,我们来谈谈递归是什么意思。
在编程语言中,递归函数调用意味着从同一个函数中调用一个函数。所以你可以在这段伪代码中看到它的样子:
def recurse():
recurse() # <- Recursive function call
def main():
recurse() # <- Normal function call
花一分钟想想为什么这里的伪代码有问题。
想象一下如果我们现在运行main函数会发生什么?
我们实际上会收到一个RecursionError异常。实际上,我们所做的就是将程序放入一个无限循环中!这是不好的。所以当我们写递归函数时,我们需要注意我们的代码包含一个终端条件,以确保在某个点上递归调用将结束。
让我们再来看看洋葱削皮器,这一次我们将进行一些修改,以应用递归原则而不是嵌套循环。
def peel(onion, layer_count) -> int:
onion.removelayer()
if onion.is_layer():
return peel(onion, layer_count + 1) # recursive call
else:
return layer_count
def countlayers_recursively():
onions = Onion(), Onion(), Onion()
layers = 0
for onion in onions:
layers += peel(onion, 0)
print(layers)
所以我们在这里写的代码,可能不是演示递归的价值的最好的例子,但我认为通过这种方式,内化使用递归逻辑的目标会更容易一些。
幸运的是,洋葱不是一个非常复杂的结构,因此递归的优点在这里不太明显。让我们考虑另一个复杂的比喻。
树怎么样?
稍微复杂一点,对吧?假设你想要数一棵树上的叶节点数,想想你如何在不使用递归的情况下做到这一点。每个树枝都有可能包含无限的枝干和无限的叶子!当然,真正的树受到物理和环境条件的限制,因此存在一些有限的边界,但如果有足够的内存,计算树是无限的。
让我们更实际地看看使用嵌套列表。
def simple_sum(num_list: [int]) -> int:
total = 0
for n in num_list:
total += n
return total
print(simple_sum([1,2,3,4,5,6]))
def list_sum(num_list: [[int]]) -> int:
total = 0
for l in num_list:
for n in l:
total += n
return total
print(list_sum([[1],[2,3],[4,5,6]]))
def complex_sum(num_list: [int or [int]]) -> int:
total = 0
for obj in num_list:
if type(obj) == list:
for n in obj:
total += n
else:
total += obj # if not a list, must be integer
return total
print(complex_sum([1,2,[3,4],[5],6]))
现在运行这些函数,可能是不必要的。您可以假设每个函数将返回作为函数参数传递的整数的和。但是,如果你愿意,可以花一分钟时间来运行每一个。
相反,随着列表参数复杂度的增加,请注意每个函数的复杂度也在增加。请注意每个参数如何创建要考虑的额外规则,以及每个函数如何需要额外的逻辑来处理复杂性。
当我们到达complex_sum时,我们处理的是两个嵌套层和一个整数。但是,就像拥有无限分支和叶子的树一样,当我们需要添加第三级嵌套列表时,会发生什么呢?第四层呢?
这就是递归变得真正有价值的地方。我们可以用一个递归函数替换这里的所有三个sum函数以及支持深层嵌套所需的任何其他函数。
def recursive_sum(nested_list) -> int:
total = 0
for obj in nested_list:
if type(obj) == list:
total += recursive_sum(obj) # <- recursive call
else:
total += obj # if not a list, must be integer
return total
print(recursive_sum([1,2,3,4,5,6]))
print(recursive_sum([[1],[2,3],[4,5,6]]))
print(recursive_sum([1,2,[3,4],[5],6]))
好,最后一个想法。猜猜还有什么类似树的分支或无限嵌套列表(切换显示答案)?
计算机文件系统!