【问题标题】:Recursion function in two types for Fibonacci sequence斐波那契数列的两种递归函数
【发布时间】:2020-03-27 12:48:31
【问题描述】:

这是使用递归的斐波那契数列的原始代码

def rec(n):
if n<=1:
    return n
else:
    return ( rec(n-1) + rec(n-2))

n=int(input())

上面的代码在第 50 个学期左右变得非常慢。

下面我返回的​​代码基本上也是递归的。

n=int(input())
n1,n2,count=0,1,0
def rec(n,n1,n2,count):
    if count<n:
        print(n1)
        nth=n1 + n2
        n1=n2
        n2=nth
        count+=1
        rec(n,n1,n2,count)
rec(n,n1,n2,count)

我的问题是这两种方法都遵循递归(就像真正的递归)?

【问题讨论】:

  • 什么是真正的递归
  • 喜欢第一个。
  • 两者都称为递归函数。没有什么能比得上 Real Recursion。第一个函数有两个递归调用,第二个函数有一个递归调用。你对Real Recursion的定义到底是什么?我从来没有听说过这样的 TBH

标签: python recursion fibonacci


【解决方案1】:

这两个函数都是递归的,但由于最后一个函数将调用自身作为函数中的最后一个动作,因此也被描述为尾递归

尾递归函数可以很容易地转换成循环:

def rec(n, n1=0, n2=1, count=0):
    while count < n:
        print(n1)
        n1, n2, count = n2, n1 + n2, count +1

【讨论】:

  • 第一个函数也在最后一个动作中调用自己。这不是使第一个也是尾递归的吗?
  • @NikhilKaushal,最后一个动作并不意味着代码的列表行。您在最后一行代码return (rec(n-1) + rec(n-2)) 中看到有两个递归调用,因此第二个必须等待第一个完成。
  • 在调用完成后,它们的返回值必须相加。
【解决方案2】:

嗯,它们都是递归的,因为你在内部调用函数a()。因此,这两个函数的主要区别在于,第一个函数有两个递归调用,第二个函数只有一个。

那么现在,在另一件事上:

上面的代码在第 50 个学期左右变得非常慢。

嗯,你可以做一些有趣的事情来更快地完成它。看,由于斐波那契数列的递归定义,您正在做不止一个相同的计算。

例如:假设您要计算 fibonacci(5),因此您需要计算 fibonacci(4)fibonacci(3)。但是现在,对于fibonacci(4),您需要计算fibonacci(3)fibonacci(2),依此类推。但是等等,当你完成计算 fibonacci(4) 分支时,你已经计算了 3 和 2 的所有斐波那契,所以当你从第一个递归调用返回到另一个分支 (fibonacci(3)) 时,你已经计算了它。那么,如果有一种方法可以存储这些计算以便我可以更快地访问它们呢?您可以使用Decorators 创建一个memoize 类(某种内存以避免重复计算):

这样我们会将fibonacci(k) 的每个计算存储在dict 上,并且我们将在每次调用之前检查它是否存在于字典中,如果True 则返回,否则计算它。这种方式更快

class memoize:
    def __init__(self, function):
        self.f = function
        self.memory = {}

    def __call__(self, *args):
        if args in self.memory:
            return self.memory[args]
        else:
            value = self.f(*args)
            self.memory[args] = value
            return value

@memoize
def fib(n):
  if n <= 1:
    return n
  else:
    return fib(n-1) + fib(n-2)

r = fib(50)
print(r)

您可以看到,没有memoization 花费的时间太长,而使用memoization 只花费了0.263614

希望对你有帮助

【讨论】:

  • 还有一个问题,为什么包括网站在内的每个人都给我们举了First code的例子来教我们递归。因为当我们移动到喜欢 50 多个术语时它是没有效率的。第二个代码非常有效。
  • @NikhilKaushal,认为因为Fibonacci 序列是一个易于理解的经典数学递归示例。但是,也许他们可以将其用作稍后在网站/书籍中介绍此类技术的一种方式。其他递归问题,但对于初学者来说并不那么容易掌握是Hanoi Towers
【解决方案3】:

如果一个函数调用自己,它被认为是递归的。

您的两个实现之间的区别在于,第一个实现大约 2**n 次调用自己,第二个调用自己大约 n 次。

对于 n = 50,2**50 是 1125899906842624。这是很多调用!难怪需要很长时间。 (例如:想想rec(10) 在计算rec(50) 时被调用的次数。很多很多很多次。)

虽然您的两个函数都是递归的,但我想说后者是“前向迭代”,因为您实际上是在通过斐波那契数列向前移动;对于您的第二个rec(50),该函数仅递归大约 50 次。

一种使递归调用更快的技术称为memoization。见"Memoization" on Wikipedia。如果之前已经计算过答案,它会立即返回......因此不会“重新递归”。

【讨论】:

  • 还有一个问题,为什么包括网站在内的每个人都给我们举了First code的例子来教我们递归。因为当我们移动到喜欢 50 多个术语时它是没有效率的。第二个代码非常有效
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-07-27
  • 2020-03-21
  • 2012-11-19
  • 2021-12-01
  • 2016-11-07
  • 2012-02-16
  • 1970-01-01
相关资源
最近更新 更多