javascript - 理解内存的斐波那契函数

标签 javascript recursion fibonacci memoization

该函数取自 here :

function fib(n,memo) {
   memo = memo || {}
   if (memo[n]) {
      return memo[n]
   }
   if (n <= 1) {
      return 1
   }
   return memo[n] = fib(n - 1, memo) + fib(n - 2, memo)
}

这是一个有效的内存递归函数,它接受参数 n 并返回第 n: 个斐波那契数。

我无法理解的是,具有指定值的备忘录对象如何在不同的递归函数调用中可见?我认为下面的图片正确地描述了使用参数 4 调用该函数时所发生的情况,但如果不是,请纠正我:

Recursion chart

记下图中标有 1. 和 2. 的函数调用以及它们各自的注释。数字 2 (fib(1)) 是如何获得包含数字 1 (fib(2)) 中分配的值的备注参数的?提供给数字 2 (fib(1)) 的备忘录不应该与提供给其上方的 fib(3) 调用相同吗,这将是一个空对象?

我知道备忘录对象可能只是一个全局变量,并且对它的所有赋值都会在所有递归中自动可见,但我只是想了解这种特定技术的工作原理。

希望这个描述是有道理的。谢谢。

最佳答案

简介:为什么会这样?

您在评论中表示,按值传递描述有助于澄清这一点。但基本点是,在递归调用中,缓存是从外部传递给它们的。他们获得共享值引用的副本。初始调用接受缓存,如果未提供,则创建一个新缓存。

如果你愿意,你可以这样调用它:

const fibCache = {};

fib (5, fibCache) //=> 8

fib (7, fibCache) //=> 21

并且您不会重新计算 fib (2)fib (3)fib (4)fib (5) 在第二次调用中。但必须保留它很烦人。下面我们创建一个辅助函数,让我们创建具有永久缓存的内存函数。

内存策略

这可能被认为是较低级别的内存。该函数会被内存,但仅限于初始调用及其生成的所有递归调用的长度。有多种方法可以内存函数,以便缓存在调用之间持续存在。

如果我们从像这样的非内存版本开始:

function fib1 (n) {
  if (n <= 1) {
    return 1
  }
  console .log (`Calculating fib1 (${n})`)
  return fib1 (n - 1) + fib1 (n - 2)
}

console .log (`fib1 (5) returns ${fib1 (5)}`)
console .log ('------')
console .log (`fib1 (5) returns ${fib1 (5)}`)

我们将得到如下输出:

Calculating fib1 (5)
Calculating fib1 (4)
Calculating fib1 (3)
Calculating fib1 (2)
Calculating fib1 (2)
Calculating fib1 (3)
Calculating fib1 (2)
fib1 (5) returns 8
------
Calculating fib1 (5)
Calculating fib1 (4)
Calculating fib1 (3)
Calculating fib1 (2)
Calculating fib1 (2)
Calculating fib1 (3)
Calculating fib1 (2)
fib1 (5) returns 8

显然,当我们尝试更大的值时会遇到问题。

使用您提供的版本:

function fib2 (n,memo) {
   memo = memo || {}
   if (memo[n]) {
      return memo[n]
   }
   if (n <= 1) {
      return 1
   }
   console .log (`Calculating fib2 (${n})`)
   return memo[n] = fib2 (n - 1, memo) + fib2 (n - 2, memo)
}

console .log (`fib2 (5) returns ${fib2 (5)}`)
console .log ('------')
console .log (`fib2 (5) returns ${fib2 (5)}`)

我们减少了这个,以便内部调用不再重复,但在外部调用之间,我们仍然重复:

Calculating fib2 (5)
Calculating fib2 (4)
Calculating fib2 (3)
Calculating fib2 (2)
fib2 (5) returns 8
------
Calculating fib2 (5)
Calculating fib2 (4)
Calculating fib2 (3)
Calculating fib2 (2)
fib2 (5) returns 8

如果我们可以将缓存存储在函数调用外部的某个地方,我们就可以制作一个根本不重复的版本:

const memoize = (fn) => {
  const cache = {}
  return function (x) {
    if (!(x in cache)) {
      cache [x] = fn (x)
    }
    return cache [x]
  }
}

const fib3 = memoize (function (n) {
  if (n <= 1) {
    return 1
  }
  console .log (`Calculating fib3 (${n})`)
  return fib3 (n - 1) + fib3 (n - 2)
})

console .log (`fib3 (5) returns ${fib3 (5)}`)
console .log ('------')
console .log (`fib3 (5) returns ${fib3 (5)}`)

Calculating fib3 (5)
Calculating fib3 (4)
Calculating fib3 (3)
Calculating fib3 (2)
fib3 (5) returns 8
------
fib3 (5) returns 8

这里,我们将缓存存储在通过调用 memoize 帮助器生成的闭包中。对于许多问题来说,这是一个更令人满意的版本。这也适用于没有递归但计算很耗时的情况。

关于javascript - 理解内存的斐波那契函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66264630/

相关文章:

C++ recursive_directory_iterator 遗漏一些文件

c++ - 当我们使用斐波那契数列的递归方法调用 fib(6) 时,fib(3) 被调用多少次?

javascript - 带有 mustache.js 的行 strip 化和第一个/最后一个类

javascript - Angularjs 不从函数打印

javascript - div 上的 JQuery 鼠标悬停和鼠标移出文本

java - 与 Math.pow() 一起使用时无法获得正确的数字乘积

java - 2 Java中函数的递归调用

c - 动态规划问题-斐波那契数列

c - 在 C 中子方法内使用循环的奇怪结果

javascript - 如何在sencha touch中改变按钮的按下状态