UCB CS61a Lab03 递归、树形递归(Python)

提示:本文内容来源于UCB CS61A课程,详情请点击CS 61A: Structure and Interpretation of Computer Programs


前言

本文为UCB CS61a 课程 Lab03 的内容


提示:以下是本篇文章正文内容,叙述部分参考[Lab 3: Recursion, Tree Recursion](https://inst.eecs.berkeley.edu/~cs61a/su20/lab/lab03/),由本人与chatgpt共同翻译,作业部分以及思路描述为作者完成,仅供参考,若有错误欢迎评论指正

CS61a Lab03 递归、树形递归(Python)

实验主要内容

递归

递归函数即在函数体中直接或者间接调用自身的函数

在本门课程的实验描述中,提出递归函数有三个重要组成部分:

  1. 基线条件,即函数想要解决的问题的最简单的形式,也可以称之为基本情况,设计函数时可以在此处设立递归出口
  2. 递归,即缩小问题的规模后,再次调用函数自身
  3. 调用递归函数解决整个问题

Problem Solving with Algorithms and Data Structures using Python中,递归的描述如下

递归是一种解决问题的方法,它涉及将一个问题分解成越来越小的子问题,直到得到一个足够小的问题,可以轻松地解决它。
所有递归算法都必须遵守三个重要法则:

  1. 递归算法必须有一个基本情况
  2. 递归算法必须改变其状态并向基本情况移动
  3. 递归算法必须递归地调用自己

后续实现将会要求学生编写递归函数,实验描述给出了几个通用提示:

编写函数时,需要假设该函数已经可以正常实现其功能,而不用考虑如何实现
想办法将当前要解决的问题简单化
考虑递归的基线条件/只因本情况,设计递归出口
借助迭代/循环帮助理解递归的思路

树形递归

树形递归函数是指一个递归函数在执行时会多次调用自身,从而形成一系列类似树形结构的调用

斐波那契数是一个树形递归的经典案例

def fib(n):
    if n == 0 or n == 1:
        return n
    return fib(n - 1) + fib(n - 2)
fib(4)
fib(2)
fib(0)
fib(1)
fib(3)
fib(1)
fib(2)
fib(0)
fib(1)

每个fib(i)节点都表示一次对fib的递归调用,每个递归调用会再次进行两个递归调用,从而形成一系列类似树状的调用fib(0)fib(1)不进行任何递归调用,因为它们是该函数的基线条件

在单个步骤中探索多个可能性或选择时使用树形递归非常有效。在这些类型的问题中,每个选择或一组选择都会进行一次递归调用。课程组给了一些例子:

Given a list of paid tasks and a limited amount of time, which tasks should you choose to maximize your pay? This is actually a variation of the Knapsack problem, which focuses on finding some optimal combination of different items.
Suppose you are lost in a maze and see several different paths. How do you find your way out? This is an example of path finding, and is tree recursive because at every step, you could have multiple directions to choose from that could lead out of the maze.
Your dryer costs $2 per cycle and accepts all types of coins. How many different combinations of coins can you create to run the dryer? This is similar to the partitions problem from the textbook.

要求解决的问题

判断Python会显示的内容

Q1:递归
$ python3 ok -q recursion-wwpd -u --local
=====================================================================
Assignment: Lab 3
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unlocking tests

At each "? ", type what you would expect the output to be.
Type exit() to quit

---------------------------------------------------------------------
Recursion > Suite 1 > Case 1
(cases remaining: 1)

What would Python display? If you get stuck, try it out in the Python
interpreter!

>>> def f(a, b):
...     if a > b:
...         return f(a - 3, 2 * b)
...     elif a < b:
...         return f(b // 2, a)
...     else:
...         return b
>>> f(2, 2)
? 2
-- OK! --

>>> f(7, 4)
? 4
-- OK! --

>>> f(2, 28)
? 8
-- OK! --

>>> f(-1, -3)
? Infinite
-- OK! --

---------------------------------------------------------------------
OK! All cases for Recursion unlocked.
Q2:地心之旅
$ python3 ok -q sr-wwpd -u --local
=====================================================================
Assignment: Lab 3
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unlocking tests

At each "? ", type what you would expect the output to be.
Type exit() to quit

---------------------------------------------------------------------
Self-Reference > Suite 1 > Case 1
(cases remaining: 1)

What would Python display? If you get stuck, try it out in the Python
interpreter!

>>> def crust():
...   print("70km")
...   def mantle():
...       print("2900km")
...       def core():
...           print("5300km")
...           return mantle()
...       return core
...   return mantle
>>> drill = crust
>>> drill = drill()
? 70km
-- OK! --

>>> drill = drill()
? 2900km
-- OK! --

>>> drill = drill()
(line 1)? 5300km
(line 2)? 2900km
-- OK! --

>>> drill()
(line 1)? 5300km
(line 2)? 2900km
(line 3)? Function
-- OK! --

---------------------------------------------------------------------
OK! All cases for Self-Reference unlocked.

代码练习

Q3:帕斯卡三角

以下为帕斯卡三角的一部分:

1
1 1
1 2 1
1 3 3 1
1 4 6 4 1

帕斯卡三角中的每个数字均为其上方的数字与它左上方直接的数字之和。如果该位置没有数字,则该位置为0

实现一个pascal(row, column)函数,其参数为(行序号,列序号),求得在帕斯卡三角中该位置的值。行和列从零开始,第一行/列是0而不是1。

def pascal(row, column):
    """Returns a number corresponding to the value at that location
    in Pascal's Triangle.
    >>> pascal(0, 0)
    1
    >>> pascal(0, 5) # Empty entry; outside of Pascal's Triangle
    0
    >>> pascal(3, 2) # Row 4 (1 3 3 1), 3rd entry
    3
    """
    "*** YOUR CODE HERE ***"
    if row == 0 and column == 0:
        return 1
    if row < 0 or column < 0 or column > row + 1:
        return 0
    return pascal(row-1, column) + pascal(row-1, column-1)

以上函数实现满足测评脚本要求

lyx@Hetu:lab03$ python3 ok -q pascal --local
=====================================================================
Assignment: Lab 3
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running tests

---------------------------------------------------------------------
Test summary
    1 test cases passed! No cases failed.
Q4:重复、重复

实现函数repeated

该函数的两个参数分别为fn

其返回值为参数函数f进行n次复合后的函数

复合两函数的辅助函数compose1已给出

要求使用递归来实现(课程组提供的评分脚本拥有对代码中是否含有循环(while, for),是否调用自身(repeat)的检测)

def compose1(f, g):
    """"Return a function h, such that h(x) = f(g(x))."""
    def h(x):
        return f(g(x))
    return h

def repeated(f, n):
    """Return the function that computes the nth application of func (recursively!)."""
    "*** YOUR CODE HERE ***"
    if n == 0:
        return lambda x: x
    return compose1(f, repeated(f, n-1))

以上实现符合检测脚本要求

$ python3 ok -q repeated --local
=====================================================================
Assignment: Lab 3
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running tests

---------------------------------------------------------------------
Test summary
    1 test cases passed! No cases failed.
Q5:数字8的数量

统计一个正整数中数字8的数量,要求使用递归,禁止使用赋值语句

使用//缩减问题规模,同时检查末尾数字是否为8

递归出口为问题规模最小,即x缩减到0

def num_eights(x):
    """Returns the number of times 8 appears as a digit of x."""
    "*** YOUR CODE HERE ***"
    if x > 0:
        if x % 10 == 8:
            return 1 + num_eights(x // 10)
        else:
            return 0 + num_eights(x // 10)
    else:
        return 0

以上实现符合测评脚本要求

$ python3 ok -q num_eights --local
=====================================================================
Assignment: Lab 3
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running tests

---------------------------------------------------------------------
Test summary
    1 test cases passed! No cases failed.
Q6: 乒乓

乒乓序列从1开始计数,始终在向上或向下计数。在第k个元素处,如果k是8的倍数或包含数字8,则方向会切换。以下是乒乓序列的前30个元素,其中第8个,第16个,第18个,第24个和第28个元素处使用括号标记了方向交换:

Index1234567[8]9101112131415[16]17[18]1920212223[24]252627[28]2930
PingPong Value1234567[8]7654321[0]1[2]10-1-2-3[-4]-3-2-1[0]-1-2

实现一个函数pingpong,该函数返回乒乓序列的第n个元素,不使用任何赋值语句。

可以使用之前定义的函数 num_eights。

要求使用递归实现,如果使用任何赋值语句,则无法通过测试

正向实现很好实现

def pingpong(n):
    """Return the nth element of the ping-pong sequence."""
    "*** YOUR CODE HERE ***"
    from operator import add, sub
    result = 1
    ops = {0: add, 1: sub}
    op = 0
    for i in range(2, n+1):
        result = ops[op](result, 1)
        if i % 8 == 0 or num_eights(i) > 0:
            op = 1 - op        
    return result

但是要求用递归完成,不能有赋值,大概也不能用循环

我自己写了一个比较邪门儿的实现

除了第一个数以外,乒乓序列中的某一个数,一定是由前一个数加/减1得来的

把第n个数字到第n+1个数字的操作记为down

如果down为Tru的话,说明从第n个数字到第n+1个数字需要减少1

如果down为False,说明从第n个数字到第n+1个数字需要增加1

为求第n个数字,那么就需要知道第n-1个

为求第n-1个数字,那么就需要知道第n-2个

为求第2个,需要知道第1个

在这个过程中,如果n恰好是8的整数倍或者含有数字8,那么我们需要调整down的值(False变成True,True变成False)

比如为求第八个,需要第七个,已知第八个到第九个是要减1(down为True)

那么我们在通过第七个求第八个时,需要把down变为False,即 第八个 = 第七个 + 1,同时把更改后的down值传给递归求第七个的过程

知道遇到递归出口,即我们求到了第1个数字了,这时我们需要检查第一个到第二个的操作(与第二个到第三个的操作相同)的down值是否为False,即第一个数到第二个数只能+1,如果down为True的话,说明该操作是非法的,抛出一个异常。尝试另一种操作

整个过程如下:

  1. 选择down值为False,表示从第n个数到第n+1个数要 + 1
  2. 如果n为1,那么我们需要检测down的值是否为True。如果是,那么表明从第一个数到第二个数需要减一,与题设不服,回到第一步并将初始down值设为True;如果不是,那么符合题设,第1个数的值就求得了
  3. 如果n是八的整数倍,或者n中有某位数字是8,那么说明第n-1到第n个数的操作与从第n个数到第n+1个数的操作相反,将down设为 not down;否则跳过该步骤
  4. 根据down的值,将第n-1个值与第n个值之间的关系表示出来,sequence[n] = sequence[n-1] down 1
  5. 将n自减1,回到第2步求第n-1个值

函数的实现如下

def pingpong(n):
    """
    Return the nth element of the ping-pong sequence.
    """
    "*** YOUR CODE HERE ***"
    def ping_pong(n, down):
        if n == 1:
            if down == True:
                raise ValueError
            return 1
        if n % 8 == 0 or num_eights(n) > 0:
            if not down == True:
                return ping_pong(n-1, not down) - 1
            else:
                return ping_pong(n-1, not down) + 1
        else:
            if down == 1:
                return ping_pong(n-1, down) - 1
            else:
                return ping_pong(n-1, down) + 1
    try:
        return ping_pong(n, False)
    except ValueError:
        return ping_pong(n, True)

能过脚本的测评,但不知道为啥,感觉我这思路好🌶🐓啊

$ python3 ok -q pingpong --local
=====================================================================
Assignment: Lab 3
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running tests

---------------------------------------------------------------------
Test summary
    1 test cases passed! No cases failed.

看了一下CS61a课程组给的提示视频,居然可以正着来(从1开始求),果然我写得就是🌶🐓

按照提示的思路完成的函数实现如下

def pingpong(n):
    """
    Return the nth element of the ping-pong sequence.
    """
    "*** YOUR CODE HERE ***"
    def change_direction(direction):
        return not direction

    def meet_eight(index):
        return index % 8 == 0 or num_eights(index) > 0

    def ping_pong(value, index, increase):
        if index == n:
            return value
        if not meet_eight(index):
            if increase == True:
                return ping_pong(value+1, index+1, increase)
            else:
                return ping_pong(value-1, index+1, increase)
        else: # meet eight, need to change direction
            if change_direction(increase) == True:
                return ping_pong(value+1, index+1, change_direction(increase))
            else:
                return ping_pong(value-1, index+1, change_direction(increase))

    return ping_pong(1, 1, True)

也能通过测试,但是人家这个思路就很顺畅,不像我的那么别扭

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值