Ruby中的递归Fibonacci

时间:2017-07-08 00:56:47

标签: ruby recursion fibonacci

本周是我第一次做递归。我能够解决的问题之一是Fibonacci的序列到第n个数字;在弄乱它5分钟后并不难。

但是,我无法理解为什么它适用于当前的return语句。

return array if num == 2

如果我推送到数组,它不起作用,如果我创建一个新的变量序列并推送到它,它会返回正确的答案。我很酷,但我的基础案例是返回数组,而不是序列。我最初将序列推送到数组,结果不是fibs序列。当我尝试看到如果我按下序列数组会发生什么时,我才解决了这个问题。

我希望有人可以解释幕后发生的事情,堆栈可能是什么以及问题是如何运作的,而不仅仅是让它发挥作用。

我理解递归到一定程度,并且通过某种方式直观地通过假设来使其工作,但我觉得好笑而不是真正了解其背后的所有原因。

def fib_seq(num)
  return [0] if num == 1
  return [] if num == 0

  array = [0, 1]
  return array if num <= 2 

  seq = fib_seq(num - 1)  
  seq << seq[-2] + seq[-1]
end 

3 个答案:

答案 0 :(得分:2)

通过删除临时In [130]: re.findall(r'(?<!\d|M)(?:-?[\d]+\.[\d]+)|M(?=,|$)', line, re.M) Out[130]: ['-94.5930', '39.1230', '79.00', '73.90', '84.41', '220.00', '4.00', '0.00', '29.68', '1003.90', '10.00', 'M', 'M', 'M', 'M', '9500.00', 'M', 'M', 'M', 'M'] 变量,可以简化代码。这是一种分心。它也适用于array; num == 2将由其他基本案例处理。 num < 2是非法的,应该通过错误检查来处理。

我还在一个明确的回报中添加了。显式返回使得返回的内容非常明显,这有助于理解递归。在这种情况下,它是num < 0。 (“明确的回报是邪恶的!”所有Ruby风格的人都会哭。坚韧的饼干。好的风格不是绝对的。)

seq

现在更清楚一点def fib_seq(num) # Error check if num < 0 then raise ArgumentError, "The number must be a positive integer" end # Terminating base cases return [] if num == 0 return [0] if num == 1 return [0,1] if num == 2 # Recursion seq = fib_seq(num - 1) # The recursive function seq << seq[-2] + seq[-1] return seq end 是递归的三个基本情况之一。这些是终止递归的终止条件。但处理并没有就此结束。结果不是return [0,1] if num == 2,因为在第一次返回之后,堆栈必须放松。

让我们一起走过[0,1]

fib_seq(4)

我们已达到基本情况,现在我们需要解开那堆电话。

fib_seq(4) calls fib_seq(3) fib_seq(3) calls fib_seq(2) fib_seq(2) returns `[0,1]` 的电话会从中断的地方开始。 fib_seq(3)返回的seqfib_seq(2)。它会在末尾添加[0,1]并返回seq[-2] + seq[-1]

[0,1,1]从它停止的地方开始。 fib_seq(4)返回的seqfib_seq(3)。它会在末尾添加[0,1,1]并返回seq[-2] + seq[-1]

堆栈已展开,因此我们返回[0,1,1,2]

如您所见,实际计算是向后发生的。 [0,1,1,2]f(n) = f(n-1) + f(n-2)。它会向下递归f(2) = [0,1],基本情况,然后使用f(2)的结果展开f(3),使用f(2)的结果取消f(4)等等。

答案 1 :(得分:2)

递归函数需要具有退出条件以防止它们永远运行。递归方法的主要部分如下:

seq = fib_seq(num - 1)
seq << seq[-2] + seq[-1]

在Ruby中,方法的最后一个表达式被认为是该方法的返回值,因此上面的行等同于:

seq = fib_seq(num - 1)
seq << seq[-2] + seq[-1]
return seq

让我们了解如果方法只包含这两行,会发生什么, num = 4:

call fib_seq(4)
  call fib_seq(3)
    call fib_seq(2)
      call fib_seq(1)
        call fib_seq(0)
          call fib_seq(-1)
            ...

显然这会导致无限循环,因为我们没有退出条件。我们总是在第一行再次调用fib_seq,因此代码最终无法到达return语句。要解决这个问题,让我们在开头添加这两行:

array = [0, 1]
return array if num <= 2

这些可以简化为:

return [0, 1] if num <= 2

现在让我们看看当我们用 num = 4调用方法时会发生什么:

call fib_seq(4)
  4 > 2, exit condition not triggered, calling fib_seq(n - 1)
  call fib_seq(3)
    3 > 2, exit condition not triggered, calling fib_seq(n - 1)
    call fib_seq(2)
    2 == 2, exit condition triggered, returning [0, 1]!
  fib_seq(2) returned with seq = [0, 1]
  add 0 + 1 together, push new value to seq
  seq is now [0, 1, 1]
  return seq
fib_seq(3) returned with seq = [0, 1, 1]
add 1 + 1 together, push new value to seq
seq is now [0, 1, 1, 2]
return seq
FINAL RESULT: [0, 1, 1, 2]

所以看起来这个方法适用于 num 的值>&gt; = 2:

def fib_seq(num)
  return [0, 1] if num <= 2

  seq = fib_seq(num - 1)
  seq << seq[-2] + seq[-1]
end

还有一个错误: num = 0且 num = 1都返回[0, 1]。我们来解决这个问题:

def fib_seq(num)
  return [] if num == 0
  return [0] if num == 1
  return [0, 1] if num == 2

  seq = fib_seq(num - 1)
  seq << seq[-2] + seq[-1]
end

稍微清理一下:

def fib_seq(num)
  return [0, 1].first(num) if num <= 2
  seq = fib_seq(num - 1)
  seq << seq[-2] + seq[-1]
end

答案 2 :(得分:0)

当人们将命令式样式突变与函数式递归混合在一起时,我总是觉得很困惑 - 如果您要进行所有重新分配和手动数组搜索,为什么还要使用递归作为循环机制呢?只需使用一个循环。

这并不是说这个程序不能以更实用的方式表达。在这里,我们将计算斐波那契数字和生成序列的问题分开 - 结果是一个非常容易理解的程序

def fib n
  def aux m, a, b
    m == 0 ? a : aux(m - 1, b, a + b)
  end
  aux n, 0, 1
end

def fib_seq n
  (0..n).map &method(:fib)
end

fib_seq 10
#=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

另一种特别有效地生成序列的方法 - 下面,我定义了一个利用4个状态变量以相对简单的方式生成序列的axuiliary函数aux

请注意与输入10的区别 - 这个与0返回[]尽管the 0th fibonacci number is actually 0

的拟议函数更接近
def fib_seq n
  def aux acc, m, a, b
    m == 0 ? acc << a : aux(acc << a, m - 1, b, a + b)
  end
  case n
    when 0; []
    when 1; [0]
    when 2; [0,1]
    else;   aux [0,1], n - 3, 1, 2
  end
end

fib_seq 10
# => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]