【问题标题】:Optimize Recursive Search优化递归搜索
【发布时间】:2014-01-10 12:17:32
【问题描述】:

我进行了一个实验来计算递归与迭代斐波那契数列的时间。有没有更好的方法来提高我的递归方法的性能?

require 'benchmark'

def fibonacci_iterative(n)
  fib_numbers = [0, 1]
  iterate = n-1
  iterate.times do
    number = fib_numbers[-2] + fib_numbers[-1]
    fib_numbers << number
  end
  p fib_numbers[-1]
end

def fibonacci_recursive(n)
  fib_number = 0
  if n == 0 || n == 1
    n
  else
    fib_number = fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
  end
end

puts Benchmark.measure {fibonacci_iterative(5)}
puts Benchmark.measure {fibonacci_recursive(5)}

5
  0.000000   0.000000   0.000000 (  0.000037)
  0.000000   0.000000   0.000000 (  0.000005)

puts Benchmark.measure {fibonacci_iterative(45)}
puts Benchmark.measure {fibonacci_recursive(45)}

1134903170
  0.000000   0.000000   0.000000 (  0.000039)
378.990000   0.330000 379.320000 (379.577337)

这是递归的固有特性吗?

【问题讨论】:

  • @DavidNehme 示例是 Java,而不是 Ruby。

标签: ruby optimization recursion fibonacci


【解决方案1】:

长运行时间不是递归的固有功能,但当您进行冗余递归计算时,通常会出现这种情况。可以使用一种称为“记忆化”的技术来避免这种情况,在这种技术中,您只计算一次值并将它们表成表格以供将来使用。

这是斐波那契数的记忆递归实现,猴子修补到 Fixnum...

class Fixnum
  @@fib_value = [0,1]

  def fib
    raise "fib not defined for negative numbers" if self < 0
    @@fib_value[self] ||= (self-1).fib + (self-2).fib
  end
end

0.fib     # => 0
1.fib     # => 1
2.fib     # => 1
5.fib     # => 5
100.fib   # => 354224848179261915075

如果你真的想变大,请使用斐波那契算法的matrix multiplication version,即 O(log n):

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

  private

  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
end

45.fib  # => 1134903170 confirms correctness

尽管输出超过 2600 行 80 列,但您几乎可以立即计算 1000000.fib 而不会破坏递归堆栈。

【讨论】:

    【解决方案2】:

    您在 Ruby 中的斐波那契实现是正确的。您可以通过以下方式重写它

    def fib(n)
      if n < 2
        n
      else
        fib(n-1) + fib(n-2)
      end
    end
    

    唯一的好处是它更简洁一点,而且你不需要使用任何额外的变量,事实上,它不是必需的。

    但除此之外,与您的算法相比,在时间方面没有成本变化。有一个few possible improvements。众所周知,递归算法比非递归版本慢。

    斐波那契递归序列的时间复杂度是O(n^2)(我将跳过计算的细节,有大量的论文和SO answers 可用于该主题)。有several variations

    一个快速的改进是添加缓存。这将减少序列中相同子数的计算。

    这是一个使用数组作为存储的非常快速而肮脏的示例。

    $cache = []
    
    def fib(n)
      $cache[n] ||= if n < 2
        n
      else
        fib(n-1) + fib(n-2)
      end
    end
    

    只是为了好玩,这里有一个更紧凑、独立的替代方案

    def fib(n)
      $fibcache    ||= []
      $fibcache[n] ||= (n < 2 ? n : fib(n-1) + fib(n-2))
    end
    

    PS。我仅使用全局变量作为示例来演示记忆模式。你应该使用更好的系统,全局变量在 Ruby 中几乎被认为是代码异味。

    【讨论】:

      【解决方案3】:

      您可以在计算递归斐波那契时尝试保存结果:

      def fibonacci_recursive(n):
          def fib_rec(n, a, b):
              if n == 1:
                  return a
              return fib_rec(n - 1, a + b, a)
          return fib_rec(n, 1, 0)
      

      您的递归代码具有指数行为:O(phi ^ n) where phi = (1 + sqrt(5)) / 2。

      编辑:这是在 Python 中(没有看到 Ruby 标记)。应该翻译得相当简单。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-28
        • 1970-01-01
        相关资源
        最近更新 更多