UCI-ICS32第二周笔记(3)

Recursion(递归)

让我们从一个例子开始。以洋葱为例,因为你可能会认为每个洋葱的发育都不一样。它们大小不同,外层和内层的数量也不同。所以每次我们剥一个新的洋葱,从表层到核心的工作都是不同的。如果我们把剥掉洋葱外层的操作看作一次操作,那么一个洋葱可能需要10次剥皮操作。另一个可能需要15个。而另一个可能只需要5个。

所以,给我们一篮子洋葱去皮,你可以想象,如果我们用手去剥每一个洋葱,那将是一个很大的工作!

但是,也许我们可以将工作自动化,从而节省一些时间、精力和不可避免的洋葱眼泪……

假设洋葱是一个Python对象,我们可以编写一些代码来剥离它。

注意:
不要太担心Onion对象。您可以通过展开下面的部分来查看,但现在,只需假设Onion对象有随机数量的层,可以一次删除一个层,并可以报告它是否仍然有层。

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 

关于Python中__init__的用法:
参见这篇文章

https://blog.csdn.net/luzhan66/article/details/82822896icon-default.png?t=M276https://blog.csdn.net/luzhan66/article/details/82822896

 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]))

好,最后一个想法。猜猜还有什么类似树的分支或无限嵌套列表(切换显示答案)?

计算机文件系统!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值