【问题标题】:recursive stack overflow in fibonacci function斐波那契函数中的递归堆栈溢出
【发布时间】:2014-11-14 21:07:34
【问题描述】:

我试图理解递归函数的堆栈溢出机制。 所以我使用了这个斐波那契函数:

def fib(n):
 if n==1 or n==2:
    return 1
 return fib(n-1)+fib(n-2)
print (fib(555))

当我尝试使用这个函数来计算 fibo(999) 时,我遇到了这个 RuntimeError: maximum recursion depth exceeded in comparison 但是当我尝试计算 fibo(555) 时,python 不会打印任何运行时错误但仍然可以工作

我知道 python 中的默认递归 deeptHs 是 1000,但我不明白为什么当我尝试查找 fibo (555) 时 python 不打印运行时错误

THK'X 适合所有人

【问题讨论】:

  • 请检查递归。使用 fib(999) 递归 aint 调用了 1000 次,但由于 fib(999) = fib(998) + fib(997)...所以它会创建一个超过 1000 的大长层次结构。
  • @simonzack 你认为这算作一个用例吗?我什至不认为这可以优化 TCO,至少不能完全使用传统方法。
  • @Abhi 错了。调用的深度约为 998。重要的是隐含的堆栈帧和调用者的堆栈帧,这会降低您实际可以使用的数量。
  • 嗨朋友,我尝试用这个例子来更好地理解递归堆栈溢出机制,我理解 999 的情况但不是 555 的情况,我认为 python 必须显示运行时错误但他没有
  • @simonzack Not going to happen。在使用递归作为主要或唯一迭代手段的语言中,尾调用优化是必要的; Python 还有其他用于迭代的习语。您的尾递归函数效率低下?不要使用(尾)递归。正如 Veedrac 所指出的,这不是尾递归函数,它要求递归调用的返回值立即返回;不过fib(n-1) 的值需要保存,直到fib(n-2) 返回并且两者可以相加。

标签: python recursion stack stack-overflow fibonacci


【解决方案1】:

可能是堆栈深度的限制而不是递归调用的数量,并且您很可能在第一次调用之前已经用完了一些堆栈深度到fib()

使用fib(555),您只会添加 555 堆栈帧,而不是555 + 554。这是因为计算的两个项是顺序完成的。换句话说,fib(555) 被调用并使用 555 个堆栈帧来完成它的工作,然后,这很重要,展开那些帧,所以你回到了之前调用fib(555)的堆栈级别。 然后调用fib(554)并且它使用大致相同帧数。

在任何阶段,您使用的堆栈帧都不会超过额外的 555 个堆栈帧。所以,从图形上看,它没有在做:

_                                      _
 \                                    /
  \           (555 levels)           /
   \__                            __/
      \                          /
       \  (another 554 levels)  /
        \______________________/

正在做的是:

_       _     _
 \     / \   /  (555 levels down, then
  \   /   \_/    back up, then down
   \_/           554, then back up again)

在任何情况下,使用递归计算斐波那契数的效率都非常低,因为对于999,您计算fib(998) 两次,一个作为第一项fib(999) 的一部分(@987654333 @ 正在完成一个堆栈级别)和一个作为第二个术语 fib(998)。然后,对于这两个fib(998) 调用中的每一个,您计算fib(997) 两次。这是一种非常耗时的方法,运行时间呈二次方上升(这个术语可能是错误的,那些有更多数学知识的人可以纠正我,但我的意思是它随着数字的平方上升)。

使用迭代解决方案要好得多,例如以下伪代码(毫无疑问,它看起来很像 Python,因为 Python 是一种理想的伪代码语言):

# Calculate Fibonacci, first term fib(0).

def fib(n):
    if n <= 1: return 1
    grandparent = 1
    parent = 1
    for i in range(n - 1):
        me = parent + grandparent
        grandparent = parent
        parent = me
    return me

for i in range(10):
    print i, fib(i)

这比递归解决方案效率更高,并且没有堆栈溢出问题。它几乎立即返回fib(999)

70330367711422815821835254877
18354977018126983635873274260
49050871545371181969335797422
49494562611733487750449241765
99108818636326545022364710601
20533741212738673391111981393
73125598767690091902245245323
403501

与我在无聊和徘徊之前等待递归解决方案的整整十分钟相反。

【讨论】:

  • 通过这个函数,我试图更好地理解递归堆栈的使用。我不明白为什么调用是 555 而不是 555+554,我认为这两个调用共享同一个堆栈,不是吗?
  • @user3896694,我会在答案中详细说明。
  • 但是我仍然有一个问题,python 中的递归深度是 1000 为什么我在尝试计算 fibo(995) 时出现运行时错误,递归函数调用的数量比限制少一点
  • @user3896694:我不知道,对不起,除了已经假设的,你已经用完了一些堆栈帧。或者可能,Python 源代码中的检查并不十分准确。底线是,Python 有一个限制来阻止失控递归,无论是在现实中发生在 990 还是 1000。Guido 不是递归的忠实粉丝,这可能就是为什么首先存在限制,足以允许有限的递归。递归,当然足以超越正常的非递归调用,但不会随波逐流。
  • 谢谢,只是修正 fibonacci(555) 函数有超过 554 和 553,因为 fibo(554) 的调用会引发两个调用,并且它们上的每个调用也会引起另外两个调用来电。最后我们将有大量或递归调用,但由于调用是按顺序完成的,它不会超过堆栈限制
猜你喜欢
  • 2012-05-06
  • 1970-01-01
  • 2011-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-01
相关资源
最近更新 更多