[CS61A]Lecture #6: Recursion

文章探讨了函数的设计哲学,包括语法规范(签名)和语义规范(预条件和后置条件)。通过示例解释了线性递归和尾部递归的概念,强调了尾递归的优化潜力。此外,还讨论了递归的思想,将其与归纳法联系起来,并提到了避免无限递归的重要性。最后,介绍了相互递归及其在确定数字奇偶性中的应用。
摘要由CSDN通过智能技术生成

仅用作个人学习记录
Reference:
https://inst.eecs.berkeley.edu/~cs61a/sp21/

函数的哲学

在这里插入图片描述

Syntactic specification(Signature)规定了调用函数的语法,包括需要几个参数。

Semantic specification 即函数注释部分,规定了:

  • Precondition 先决条件是对函数调用者的要求;
  • Postcondition 后置条件是函数设计者的承诺。

在理想条件下,调用者仅凭语法规范和语义规范就可使用函数的功能,而不需要了解函数的主体部分。

调用者(客户)关心的是提供怎样的参数和使用结果,但不关心结果是如何计算的。函数实现者关心的是结果如何计算,而不是参数从哪里来。从客户的角度来看,sqrt是计算函数平方根的各种方法的抽象,叫做功能抽象。

编程在很大程度上是关于选择抽象概念,从而设计清晰、快速和可维护的程序。在设计大型程序时,就是在不断抽象,分而治之,从而将一个复杂问题分解为较小的简单问题。

简单的线性递归

def sum squares(N):
	"""Return The sum of K**2 for K from 1 to N (inclusive)."""
	if N < 1:
		return 0
	else: # Use the comment!!
	return sum squares(N - 1) + N**2

这是一个简单的线性递归,每个函数实例化一次递归调用。

sum squares(3) 	=> sum squares(2) + 3**2
				=> sum squares(1) + 2**2 + 3**2
				=> sum squares(0) + 1**2 + 2**2 + 3**2
				=> 0 + 1**2 + 2**2 + 3**2 => 14

在这里插入图片描述

尾部递归

在第三讲中,我们看到了一种特殊的递归,它与迭代密切相关。下面是这个例子的一个变体和一个相应的迭代版本,并标明了对应关系(见方框)。

在这里插入图片描述

右边的版本是一个尾部递归函数,意味着递归调用要么是返回值,要么是最后执行的动作。上述例子就是将返回值分解为部分和,返回部分和,最终得到返回值。

尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的智能编译器会利用这种特点自动生成优化的代码,即将递归优化为循环,一般来说递归操作会不断压栈,占用大量空间。但 Python 不会这么做(将尾递归优化为循环)。

尾部递归是线性递归的一种。递归和循环一般来说可以相互转换。

递归思想

按照函数的哲学,我们仅凭函数的文档注释就可以使用这个函数,而不关心函数是这怎样实现的。因此在设计函数时,同样也可以使用该函数,即在函数实现内部调用自身。

在上述的sum squares 函数的实现中,可以观察到:

  • 基准条件,即递归终止的情况,如果没有要加和的数时,结果是 0 (N < 1)
  • 否则,结果就是 N**2 加上 1 到 N-1 的平方和
  • 我们有一个函数(本身)可以计算平方和,调用这个函数
  • 当 N >= 1时,就返回 N**2 + sum squares(N − 1)

只要我们能保证我们会达到基准条件(避免无限递归),这种“递归的信仰之跃”就会奏效。

避免无限递归

  • 存在基准条件,即存在结束递归的条件。

  • 确保每次递归时,递归的问题规模在减小。

  • 在有限的步骤内可以达到基准条件。

递归与归纳法

在数学中,我们有各种各样的归纳法来证明一个属性P(x)对某个集合中的所有x都成立。

如对整数进行多米诺骨牌归纳的方法:

  • 证明「 第一张骨牌会倒。」

  • 证明 「只要任意一张骨牌倒了,其下一张骨牌也会因為前面的骨牌倒而跟著倒。」

  • 则可下结论:所有的骨牌都会倒下。

即如果P(b) (the base case)成立,且对于所有的 k>b 有 P(k-1)成立可推出 P(k) 成立,就有任意k>b,P(k)成立。

进一步推广:

  • 如果 P 是具有≺关系的集合上的属性,该属性要在有限步数内返回
  • 如果你可以证明当 P(y) 对所有 y≺x 为真时,P(x) 也为真,
  • 那么 P(x) 对所有x为真。

递归思维是一种归纳性思维。我们要证明的属性是,我们的函数对任何大小的输入都有效。

归纳法告诉我们,如果想要函数对所有的输入都有效,必须有:

  • 每当我们的函数对所有比给定输入小的参数起作用时,它也一定对该输入起作用。
  • 任何输入的序列中,每一个都比它的前一个小,最终一定满足结束条件。

相互递归

当一个递归过程被划分为两个相互调用的函数时,被称为相互递归。考虑以下非负整数的偶数和奇数的定义:

  • 如果一个数比奇数大一,则这个数是偶数
  • 如果一个数比偶数大一,则这个数是奇数
  • 0 是偶数

利用这个定义,我们可以实现相互递归的函数来确定一个数字是偶数还是奇数。

def is_even(n):
    if n == 0:
        return True	    
    else:
		return is_odd(n-1)
def is_odd(n):
	if n == 0:
	    return False
	else:
	    return is_even(n-1)
result = is_even(4)

通过打破两个函数之间的抽象边界,可以将相互递归的函数变成一个单一的递归函数。在这个例子中,is_odd 的主体可以并入 is_even 的主体,确保在 is_odd 的主体中用 n-1 替换 n,以反映传递到它的参数。

def is_even(n):
	if n == 0:
        return True
    else:
        if (n-1) == 0:
        	return False
        else:
        	return is_even((n-1)-1)

相互递归并不比简单递归更神秘、更强大,它提供了一种在复杂递归程序中保持抽象的机制。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值