微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

由于递归函数,理论函数堆栈的最大深度?

如何解决由于递归函数,理论函数堆栈的最大深度?

如果我们有一个永远不会变满的理论堆栈内存,并且我们有一个简单的递归函数

recurse(n):
    if n > 0:
        recurse(n-1)
        recurse(n-2)
    return

认为理论堆栈在 recurse(n) 的执行过程中的任何时刻最多有 n 个堆栈帧是否合理,因为 recurse(k) 不可能打开recurse(i) 的顶部如果 0 recurse(i) 调用recurse(k)(基于函数体这是不可能的)。如果我的推理是正确的,那么最大深度一定是函数栈如下所示:

(BottOM-MOST)|recursion(n)|recursion(n-1)|...|recursion(2)|recursion(1) (TOP-MOST)

解决方法

n = 0 函数调用本身有一个堆栈帧时 - 任何 n 的堆栈帧都不能少于一个 - 所以最大堆栈帧数的公式是 { {1}},不完全是 max(1,n+1)。否则你的推理是正确的,这个公式可以用归纳法证明:

  • n 的基本情况下,有一个堆栈帧,它等于 n <= 0,因为 max(1,n+1)
  • 否则当 n+1 <= 1 时,会进行两次递归调用,根据归纳假设,一次的堆栈深度为 n >= 1,另一次的堆栈深度为 max(1,n)。因此,包括 max(1,n-1) 上调用的当前堆栈帧在内的最大堆栈深度等于 n。这可以简化为 1 + max(max(1,n),max(1,n-1)),因为 1+nn 操作数中最大的一个,并且 max 根据需要等于 1+n
,

这很容易证明。为自己绘制地图 -

recurse(0)
recurse(1)
  recurse(0)
  recurse(-1)
recurse(2)
  recurse(1)
    recurse(0)
    recurse(-1)
  recurse(0)
recurse(3)
  recurse(2)
    recurse(1)
      recurse(0)
      recurse(-1)
    recurse(0)
  recurse(1)
    recurse(0)
    recurse(-1)
recurse(4)
  recurse(3)
    recurse(2)
      recurse(1)
        recurse(0)
        recurse(-1)
      recurse(0)
    recurse(1)
      recurse(0)
      recurse(-1)
  recurse(2)
    recurse(1)
      recurse(0)
      recurse(-1)
    recurse(0)

这就是为什么 fib(n) 对于大 n 不会溢出堆栈,而是长时间占用您的 CPU。对于小n,例如n = 20,结果以1,048,576 步计算,但仅使用20 帧-

function fib(x)
{ if (x < 2n)
    return x
  else
    return fib(x - 1n) + fib(x - 2n)
}

console.log("result %s",fib(20n))
// result 6765

对于像 n 这样更大的 n = 200,它需要惊人的 1,606,938,044,258,990,275,541,962,092,341,162,602,522,202,993,782,792,18 计算,但不会导致堆栈溢出,736018 次溢出。然而,即使每秒 1,000,000 次计算,也需要 50,955,671,114,250,072,156,268,658,377,807,020,642 才能完成 -

function fib(x)
{ if (x < 2n)
    return x
  else
    return fib(x - 1n) + fib(x - 2n)
}

console.log("result %s",fib(200n))
// result ...

如果您尝试运行上面的 fib(200),JavaScript 将导致您的浏览器挂起,直到太阳死了很久。刷新此选项卡,我们可以在 1 毫秒内计算出答案。 fib 的这种重写使用线性递归,计算 n = 200 将只需要 200 步和 200 帧 -

function fib(x,a = 0n,b = 1n)
{ if (x == 0n)
    return a
  else
    return fib(x - 1n,b,a + b)
}

console.time("fib(200)")
console.log("result %s",fib(200n))
console.timeEnd("fib(200)")
// result 280571172992510140037611932413038677189525
// fib(200): 1.000ms

如果您使用 while 循环,则可以在 200 步和 1 帧内完成。但这不是递归,所以在这篇文章中可能不值得讨论。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。