繁体   English   中英

了解记忆的斐波那契 function

[英]Understanding a memoized Fibonacci function

function,取自这里

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)
}

这是一个有效的记忆递归 function,它接受参数 n 并返回第 n 个斐波那契数。

我无法理解的是,如何在不同的递归 function 调用中看到带有赋值的备忘录 object? 这是一张我认为正确描述了使用参数 4 调用 function 时发生的情况的图片,但如果不是,请纠正我:

递归图

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

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

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

简介:为什么会这样?

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

如果你愿意,你可以这样称呼它:

const fibCache = {};

fib (5, fibCache) //=> 8

fib (7, fibCache) //=> 21

并且您不会在第二次调用中重新计算fib (2)fib (3)fib (4)fib (5)的值。 但是不得不保留它是很烦人的。 下面我们创建一个帮助程序 function,它允许我们创建具有永久缓存的记忆函数。

记忆策略

这可能被认为是较低级别的记忆。 function 已被记忆,但仅限于初始调用的长度和它生成的所有递归调用。 有一些方法可以记忆函数,以便缓存在调用之间持续存在。

如果我们从这样的非记忆版本开始:

 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)}`)

我们会像这样得到 output:

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

如果我们可以将缓存存储在 function 调用之外的某个位置,我们可以制作一个完全不重复的版本:

 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助手生成的闭包中。 对于许多问题,这是一个更令人满意的版本。 这也适用于没有递归但计算很耗时的情况。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM