为啥python斐波那契序列循环比递归慢?

Posted

技术标签:

【中文标题】为啥python斐波那契序列循环比递归慢?【英文标题】:Why python fibonacci sequence loop is slower than recursion?为什么python斐波那契序列循环比递归慢? 【发布时间】:2021-04-18 04:46:44 【问题描述】:

下面是著名的斐波那契数列示例

# test.py
import sys
sys.setrecursionlimit(20000)

def fib_loop(n):
    if n <= 1:
        return n
    fn, fnm1 = 1, 0
    for _ in range(2, n+1):
        fn, fnm1 = fn + fnm1, fn
    return fn

def fib_recursion(n, memo=):
    if n <= 1:
        return n
    if n not in memo:
        memo[n] = fib_recursion(n-1, memo) + fib_recursion(n-2, memo)
    return memo[n]

和大家一样,我曾经认为循环变体会比递归变体快得多。然而,实际的结果却是相当惊人的。

$ python3 -m timeit "import test; test.fib_loop(10000)"
100 loops, best of 5: 1.93 msec per loop
$ python3 -m timeit "import test; test.fib_recursion(10000)"
500000 loops, best of 5: 471 nsec per loop

我不知道为什么。有人能帮帮我吗?

【问题讨论】:

删除memo然后检查差异... 如果您不提供任何数字来计时,代码将在number=1000000 执行 (see here) 上取平均值。您记住结果,因此对于 999999 尝试,它只是对(一次)生成的 dict 的 O(1) 查找,而循环必须重新计算数字 1000000 次。 ***.com/q/1132941/3929826 【参考方案1】:

因为你正在记忆你的结果。而且您在每次迭代中都重复使用该备忘录字典。所以第一次运行很慢。在其他所有调用中,它都是一个简单的 dict-lookup。

如果你使用number=1,所以它只运行一次,你会发现第一次调用实际上更慢

>>> import sys
>>> sys.setrecursionlimit(20000)
>>>
>>> def fib_loop(n):
...     if n <= 1:
...         return n
...     fn, fnm1 = 1, 0
...     for _ in range(2, n+1):
...         fn, fnm1 = fn + fnm1, fn
...     return fn
...
>>> def fib_recursion(n, memo=):
...     if n <= 1:
...         return n
...     if n not in memo:
...         memo[n] = fib_recursion(n-1, memo) + fib_recursion(n-2, memo)
...     return memo[n]
...
>>> import timeit
>>> timeit.timeit("fib_loop(1000)", setup="from __main__ import fib_loop", number=1)
9.027599999456015e-05
>>> timeit.timeit("fib_recursion(1000)", setup="from __main__ import fib_recursion", number=1)
0.0016194200000114733

或者,如果你为每个外部调用传递一个新的 memo dict,你会得到相同的行为:

>>> timeit.timeit("fib_recursion(1000, )", setup="from __main__ import fib_recursion", number=1000)
0.38679519899999093
>>> timeit.timeit("fib_loop(1000)", setup="from __main__ import fib_loop", number=1000)
0.07079556799999409

【讨论】:

我不知道timeit函数又重用了memo变量。不是局部变量吗? @GulimProject 变量的范围无关紧要,它每次都在重用同一个对象。无论如何,这通常是记忆化的工作方式,否则它会如何工作? 哇,这真是令人惊讶。不知道默认值每次都是共享和重用的…… @GulimProject 请参阅why-are-default-arguments-evaluated-at-definition-time - dict 在函数定义中创建一次,并一直存在,直到您使用 python 重新启动文件。提供带有 list 或 dict "defaults" 的函数时,这是一个常见错误,因此您通常提供 None 作为默认值,如果您不想要这种行为,则在函数内部执行“defaultlist = defaultlist or []”(除非您想要它,那么你可以按原样使用它) @juanpa.arrivillaga 不,即使没有它也会起作用,因为我在递归调用中将memo 作为参数传递。有趣的是,即使我不通过它,该函数仍然记住结果。

以上是关于为啥python斐波那契序列循环比递归慢?的主要内容,如果未能解决你的问题,请参考以下文章

剑指offer-斐波那契数列-递归和循环-python

Python-递归复习-斐波那契-阶乘-52

递归优化的斐波那契数列

斐波那契数列

python递归求斐波那契数列前10项

Matlab:如何在没有循环或内置函数的情况下递归地获取斐波那契序列中的第 N 个元素