【问题标题】:Why does this function works?为什么这个功能有效?
【发布时间】:2021-07-24 02:29:52
【问题描述】:

使用此函数,您可以使用递归函数计算斐波那契数列,但我不确定为什么会这样,我标记了我挣扎的位置,有人可以解释一下这段代码吗?

fib <- function(n){
  if (n == 0) return(0) 
  if (n == 1) return(1)
  seq <- integer(n)  # at this point i didnt understand much at all
  seq[1:2] <- 1
  calc <- function(n) { 
    if (seq[n] != 0) return(seq[n])
    seq[n] <<- calc(n-1) + calc(n-2)
    seq[n]
  }
  calc(n)
}

【问题讨论】:

  • 我的建议是设置n 的值,然后逐行运行函数中的代码行,以便了解每个命令的作用。这将帮助您破译正在发生的事情,而不是一次运行整个函数。
  • 为了回答您的具体问题,integer(n) 创建一个长度为 n 的整数向量,其中全是零。
  • 下面的代码呢?例如为什么必须包含“if (seq[n] != 0) return(seq[n])”?
  • 我想通过保存之前的结果来让递归函数更快,没想到这样会不好……
  • 现在我明白了,我认为这不是一个坏问题,但很难理解代码的目标。如果您已经更详细地布置了一些事情(例如“这是 R 中斐波那契的基本递归实现......现在我想通过保存以前的版本来加快速度......”)。但是仅仅展示代码并说“我不明白为什么会这样”并不会得到很好的接受(如您所见)。 (如果你不理解代码,它最初是从哪里来的......?)

标签: r recursion fibonacci memoization


【解决方案1】:

在递归函数求值过程中,fib() 最终会被同一个 n 调用多次。加快计算速度的一种方法是使用 memoization,它保存了先前调用函数的值和返回值的记录。使用@AnoushArivanR 的fib 函数:

system.time(fib(30))
##    user  system elapsed 
##   3.987   0.000   3.987 
library(memoise)
fib <- memoise(fib)
system.time(fib(30))
##    user  system elapsed 
##   0.004   0.000   0.004 

事实上,现在我查看了您上面的代码,我相信它正是这样做的——但这绝对很难理解! (如果您解释说这是您正在尝试做的事情,您的问题可能会更好地被接受......)

【讨论】:

  • 感谢您亲爱的 Bolker 先生对我的帖子进行的编辑。不得不提的是,这段代码取自 Roger Peng 博士的 Mastering Software Development in R。
  • 是的,对不起,下次我会解释得更好
  • 不错的答案。我一开始没看到。我认为该功能没有做类似于记忆的事情,它正在做的事情。
【解决方案2】:

注意

Roger D. Peng 博士在 Mastering Software Development in R 中引用了 fib 函数的实现,他教会了我很多东西,我永远感激他。

如上所述,这段代码写得不好,而且不必要地复杂。这是一个更简单的版本。我们首先检查n 的值是否不小于0,然后由于序列的前两个元素是序列计算开始所必需的(每个元素是序列中两个前两个元素的总和),我们设置它们分别为01,分别对应n == 1n == 2。然后我们使用递归,这是一种函数从其主体调用自身的技术,创建一系列重复计算,直到达到n 的最大数量。例如,对于n == 3,该函数通过调用fib(1)fib(2) 来调用它自己,这两者都已经设置,依此类推。然后对于fib(4),该函数同时调用fib(3)fib(2)fib(2) 已设置,fib(3) 将通过将 fib(2)fib(1) 和 ... 我希望这个解释能帮助你理解这个想法。

fib <- function(n){
  stopifnot(n > 0)
  if(n == 1) {
    return(0)
  } else if(n == 2) {
    return(1)
  } else {
    fib(n - 1) + fib(n - 2)
  }
}

fib(7)
8

但由于某些计算被计算不止一次,例如fib(6)fib(5) 计算fib(4) 函数的执行变得更慢。为了优化,您的代码已将每个fib(n) 输出保存到名为seq 的空向量中,因此在进行任何计算之前,它会检查seq[n] 的值是否已被计算。如果是,它将被使用,如果不是,它将再次计算。这种技术称为记忆化,每当计算出新的seq[n] 时,它将是seq 向量的nth 元素,为此我们在修改对象时使用称为复杂赋值运算符的&lt;&lt;-在函数的父环境中。

【讨论】:

  • 但这会减慢例如 fib(45) 的速度,有没有办法让它更快?
  • 我会解释为什么这会很慢。
  • 在 R 等未实现 tail recursion 的语言中,递归比直接计算慢。您需要快速吗?
  • 你能解释一下为什么(以及为什么递归地实现它,如果是的话?)这不是不可能的(我将在答案中展示如何),但它肯定在做事艰难的道路。有人说“我需要你编写一个快速的斐波那契计算器,并且我需要你在 R 中实现它,并且它需要实现为递归函数”.. . ?
  • 我只是想练习如何更快地制作一个递归函数,我知道这不是一个好主意,但我只是想练习,所以我需要它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-08
  • 1970-01-01
  • 2015-10-21
  • 2018-12-12
  • 1970-01-01
  • 1970-01-01
  • 2018-04-01
相关资源
最近更新 更多