【问题标题】:Ruby Fibonacci algorithm红宝石斐波那契算法
【发布时间】:2014-08-17 18:52:28
【问题描述】:

下面是我写的一个计算斐波那契数列值的方法:

def fib(n)

    if n == 0
        return 0
    end
    if n == 1
        return 1
    end

    if n >= 2
        return fib(n-1) + (fib(n-2))
    end

end

它一直工作到 n = 14,但在那之后我收到一条消息,说程序响应时间太长(我正在使用 repl.it)。有人知道为什么会这样吗?

【问题讨论】:

  • 好吧,你必须使用递归函数吗?我认为你的程序溢出了。

标签: ruby fibonacci


【解决方案1】:

朴素的斐波那契做了很多重复计算——在fib(14)fib(4)被计算了很多次。

您可以将记忆添加到您的算法中以使其更快:

def fib(n, memo = {})
  if n == 0 || n == 1
    return n
  end
  memo[n] ||= fib(n-1, memo) + fib(n-2, memo)
end
fib 14
# => 377
fib 24
# => 46368
fib 124
# => 36726740705505779255899443

【讨论】:

  • 我曾梦想过这一点,而 Ruby 使之成为可能。
  • 非常优雅,干得好。
  • 这不是缓存而是记忆化
  • 这个方法似乎返回了你想要的数字之后的数字。 fib(5) 应该返回 3 而不是后面的数字 5。
  • @JCC - 就像计算中的一切一样 - 这是作为基于零的向量实现的,这意味着第一个元素是 fib(0)。这意味着fib(5) 检索列表中的 6th 元素...
【解决方案2】:

正如其他人所指出的,您的实现的运行时间在n 中呈指数增长。有很多更简洁的实现。

构造[O(n) 运行时间,O(1) 存储]:

def fib(n)
  raise "fib not defined for negative numbers" if n < 0
  new, old = 1, 0
  n.times {new, old = new + old, new}
  old
end

记忆递归[O(n) 运行时间,O(n) 存储]:

def fib_memo(n, memo)
  memo[n] ||= fib_memo(n-1, memo) + fib_memo(n-2, memo)
end

def fib(n)
  raise "fib not defined for negative numbers" if n < 0
  fib_memo(n, [0, 1])
end

当您只需要知道像 1_000_000.fib [O(log n) 运行时间和存储(在堆栈上)] 等非常大的阶乘时,使用平方减半的矩阵乘法的递归幂:

def matrix_fib(n)
  if n == 1
    [0,1]
  else
    f = matrix_fib(n/2)
    c = f[0] * f[0] + f[1] * f[1]
    d = f[1] * (f[1] + 2 * f[0])
    n.even? ? [c,d] : [d,c+d]
  end
end

def fib(n)
  raise "fib not defined for negative numbers" if n < 0
  n.zero? ? n : matrix_fib(n)[1]
end

【讨论】:

    【解决方案3】:

    由于您使用递归,您的程序有exponential 运行时。

    仅将递归调用扩展几个级别以向您展示原因:

    fib(14) = fib(13) + fib(12)
            = (fib(12) + fib(11)) + (fib(11) + fib (10))
            = (((fib(11) + fib(10)) + (fib(10) + fib(9))) (((fib(10) + fib(9)) + (fib(9) + fib(8)))
            = ... //a ton more calls
    

    糟糕的运行时可能会导致您的程序挂起,因为将 fib(n) 增加 1 意味着您有 TON 更多的递归调用

    【讨论】:

      【解决方案4】:

      正如 Kevin L 解释的那样,您的程序会溢出。

      相反,您可以使用这样的迭代算法:

      def fib (n)
        return 0 if n == 0
        return 1 if n == 1 or n == 2
      
        x = 0
        y = 1
      
        (2..n).each do
          z = (x + y)
          x = y
          y = z
        end
      
        return y
      end
      
      (0..14).map { |n| fib(n) }
      # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
      

      【讨论】:

      • 顺便说一句,你的代码没有运行,if之后else之前不应该有endrange也无效。我将编辑您的代码以使其正常工作,如果您不同意,请恢复/调整。
      • 好吧,我只是在昏昏欲睡的情况下写了它,我将不胜感激。
      • 斐波那契数列是:0, 1, 1, 2, 3, 5, 8, 13, 21...。您的算法没有产生正确的结果。例如,对于斐波那契数列中的第 20 个元素,它返回 10946 而不是 6765;它正在进行额外的迭代。此外,当输入为 2 时,它应该返回 1。要解决这些问题,范围应该是 (2..n),您应该添加行 return 1 if n == 2。不过我喜欢你的算法。
      【解决方案5】:

      我尝试在 repl.it 上比较两种斐波那契方法的运行时间

      require 'benchmark'
      
      def fib_memo(n, memo = {})
        if n == 0 || n == 1
          return n
        end
        memo[n] ||= fib_memo(n-1, memo) + fib_memo(n-2, memo)
      end
      
      
      def fib_naive(n)
        if n == 0 || n == 1
          return n
        end
        fib_naive(n-1) + fib_naive(n-2)
      end
      
      def time(&block) 
        puts Benchmark.measure(&block) 
      end
      
      time {fib_memo(14)}
      time {fib_naive(14)}
      

      输出

      0.000000   0.000000   0.000000 (  0.000008)
      0.000000   0.000000   0.000000 (  0.000099)
      

      如您所见,运行时完全不同。正如@Uri Agassi 建议的那样,使用记忆。这个概念在这里解释得很好https://stackoverflow.com/a/1988826/5256509

      【讨论】:

        猜你喜欢
        • 2019-07-24
        • 1970-01-01
        • 2014-09-25
        • 1970-01-01
        • 2013-03-31
        • 2016-09-11
        • 1970-01-01
        • 1970-01-01
        • 2020-01-18
        相关资源
        最近更新 更多