定义
递归与无穷大有关。我知道递归与无穷大有关。我想我知道递归与无穷大有关。他确信我认为我知道递归与无穷大有关。我们怀疑他是否确定我认为我知道......
我们认为,你认为,我们现在说服了你,我们可以永远继续这个自然语言递归的例子。递归不仅是自然语言的一个基本特征,也是人类认知能力的一个基本特征。我们的思维方式是基于递归的思维过程。即使使用非常简单的语法规则,例如“英语句子包含主语和谓语,谓语包含动词、宾语和补语”,我们也可以展示自然语言的无限可能。
认知科学家和语言学家斯蒂芬·平克 (Stephen Pinker) 是这样说的:“有几千个名词可以填满主语,几千个动词可以填满谓语,人们已经有几百万种打开句子的方式。可能的组合很快地乘以难以想象的大数。事实上,句子的全部内容在理论上是无限的,因为语言规则使用了一种叫做递归的技巧。
递归规则允许一个短语包含自身的一个例子,如她认为他认为他们认为他们认为他知道等等,无穷无尽。如果句子的数量是无限的,那么可能的想法和意图的数量也是无限的,因为实际上每个句子都表达了不同的想法或意图。”1
我们必须停止在自然语言中使用递归的短途旅行,回到计算机科学和程序中的递归,最后回到编程语言 Python 中的递归。
形容词“递归”源于拉丁语动词“recurrere”,意思是“跑回去”。这就是递归定义或递归函数所做的:它在“跑回”或返回到自身。大多数学过数学、计算机科学或阅读有关编程的书的人都会遇到阶乘,它在数学术语中定义为
啊!= n * (n-1)!,如果 n > 1 且 0!= 1
由于其简单明了,它经常被用作递归的示例。
递归的定义
递归是一种对问题进行编程或编码的方法,其中函数在其主体中一次或多次调用自身。通常,它返回此函数调用的返回值。如果一个函数定义满足递归条件,我们称这个函数为递归函数。
终止条件:递归函数必须满足一个重要的条件才能在程序中使用:它必须终止。如果每次递归调用问题的解决方案都缩小并朝着基本情况移动,则递归函数将终止。基本情况是指无需进一步递归即可解决问题的情况。如果调用中没有满足基本情况,递归可能会以无限循环结束。
例子:
4!= 4 * 3! 3!= 3 * 2! 2!= 2 * 1
替换计算值给了我们以下表达式
4!= 4 * 3 * 2 * 1
换句话说,计算机科学中的递归是一种解决问题的方法,它基于解决同一问题的较小实例。
Python 中的递归函数
现在我们来在 Python 中实现阶乘。它就像数学定义一样简单而优雅。
def factorial ( n ):
if n == 0 :
return 1
else :
return n * factorial ( n - 1 )
我们可以通过在前面的函数定义中添加两个 print() 函数来跟踪该函数的工作方式:
def factorial ( n ):
print ( "factorial has been called with n = " + str ( n ))
if n == 1 :
return 1
else :
res = n * factorial ( n - 1 )
print ( "intermediate result for " , n , " * factorial(" , n - 1 , "): " , res )
返回 res
打印(阶乘(5 ))
输出:
阶乘已被调用,n = 5 阶乘已被调用,n = 4 阶乘已被调用,n = 3 阶乘已被调用,n = 2 阶乘已被调用,n = 1 2 * factorial( 1 ) 的中间结果:2 3 * factorial( 2 ) 的中间结果:6 4 * factorial( 3 ) 的中间结果:24 5 * factorial( 4 ) 的中间结果:120 120
让我们看一下阶乘函数的迭代版本。
def iterative_factorial ( n ):
result = 1
for i in range ( 2 , n + 1 ):
result *= i
return result
对于 我 在 范围(5 ):
打印(我, iterative_factorial (我))
输出:
0 1 1 1 2 2 3 6 4 24
通常的做法是将 0 的阶乘函数扩展为参数。定义0!
为 是有道理的1
,因为只有零个对象的一个排列,即如果没有什么要排列,“一切”就留在原地。另一个原因是在一组 n 中选择 n 个元素的方法的数量计算为 n!除以 n! 的乘积!和0!。
为了实现这一点,我们所要做的就是改变 if 语句的条件:
def factorial ( n ):
if n == 0 :
return 1
else :
return n * factorial ( n - 1 )
斐波那契数列
我们关于递归的论文现在将我们引向另一个有趣的递归案例。
向日葵、黄金比例、枞树球果、达芬奇密码、Tool 的歌曲“Lateralus”和右侧的图形有什么共同点?对,斐波那契数列。我们不介绍斐波那契数列,因为它们是递归函数的另一个有用示例。尝试为这个数字序列编写递归函数可能会导致程序效率低下。事实上,如此低效以至于它不会有用。我们介绍斐波那契数列是为了向您展示递归的陷阱。
斐波那契数是以下整数值序列的数字:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
斐波那契数列定义为:
Fn=Fn-+Fn-2
和 F0=0 和 F1=1
斐波那契数列以比萨的数学家列奥纳多 (Leonardo of Pisa) 的名字命名,他更为人所知的是斐波那契。在他的书“Liber Abaci”(出版于 1202 年)中,他介绍了这个序列作为处理兔子的练习。他的斐波那契数列从 F1 = 1 开始,而在现代数学中,该序列从 F0 = 0 开始。但这对序列的其他成员没有影响。
斐波那契数是人工兔子种群的结果,满足以下条件:
- 一对刚出生的兔子,一公一母,建立初始种群
- 这些兔子可以在一个月大的时候交配,这样在第二个月末,雌性就可以生育另一对兔子
- 这些兔子是不朽的
- 从第二个月开始,一对交配的一对总是每个月产生一对新的(一男一女)
斐波那契数是n
几个月后兔子对的数量,即 10 个月后我们将有F10 兔子。
斐波那契数列很容易写成 Python 函数。它或多或少是数学定义中的一对一映射:
def fib ( n ):
if n == 0 :
return 0
elif n == 1 :
return 1
else :
return fib ( n - 1 ) + fib ( n - 2 )
迭代解决方案也很容易编写,尽管递归解决方案看起来更像定义:
def fibi ( n ):
old , new = 0 , 1
if n == 0 :
return 0
for i in range ( n - 1 ):
old , new = new , old + new
return new
我们将编写一个名称fibonacci
包含函数fib
和fibi
. 为此,您必须将以下代码复制到名为 fibonacci0.py 的文件中:
""" 包含斐波那契函数的递归和迭代实现的
模块。该模块的目的在于展示斐波那契函数的纯递归实现的低效率!"""
def fib ( n ):
""" 斐波那契函数的递归版本 """
if n == 0 :
return 0
elif n == 1 :
return 1
else :
return fib ( n - 1 ) + fib ( n - 2 )
def fibi ( n ):
""" 斐波那契函数的迭代版本 """
old , new = 0 , 1
if n == 0 :
return 0
for i in range ( n - 1 ):
old , new = new , old + 新
返回 新
输出:
覆盖 fibonacci0.py
如果您检查函数 fib() 和 fibi(),您会发现迭代版本 fibi() 比递归版本 fib() 快很多。为了了解这“快得多”可以达到多少,我们编写了一个脚本,它使用 timeit 模块来测量调用。为此,我们将 fib 和 fibi 的函数定义保存在文件 fibonacci.py 中,我们可以在下面的程序 (fibonacci_runit.py) 中导入该文件:
从 timeit 导入 计时器
t1 = Timer ( "fib(10)" , "from fibonacci import fib" )
对于 我 在 范围(1 , 20 ):
CMD = “FIB(” + STR (我) + “)”
T1 = 定时器(CMD , “从进口斐波纳契FIB” )
TIME1 = T1 。timeit ( 3 )
cmd = "fibi(" + str ( i )