【问题标题】:Optimization of Fibonacci sequence generating algorithm优化斐波那契数列生成算法
【发布时间】:2012-06-11 01:14:57
【问题描述】:

众所周知,生成斐波那契数列最简单的算法如下:

if(n<=0) return 0;
else if(n==1) return 1;
f(n) = f(n-1) + f(n-2);

但是这个算法有一些重复的计算。例如,如果您计算 f(5),它将计算 f(4) 和 f(3)。当您计算 f(4) 时,它将再次计算 f(3) 和 f(2)。有人能给我一个更省时的递归算法吗?

【问题讨论】:

  • f(n - 1) = f(n - 2) + f(n - 3) 所以f(n) = 2 * f(n - 2) + f(n - 3)。您可以缓存f(n - 2)。当然,迭代地做会更好,特别是如果你选择的语言有yield
  • @minitech 如果我想使用缓存的方式,可以给我完整的代码吗?
  • @minitech 你可以用javascript吗?

标签: recursion fibonacci


【解决方案1】:

这会执行得更快,O(n)

def fibo(n):
    a, b = 0, 1
    for i in range(n):
        if i == 0:
            print(i)
        elif i == 1:
            print(i)
        else:
            temp = a
            a = b
            b += temp
    print(b)
n = int(input())
fibo(n)

【讨论】:

    【解决方案2】:

    我已经阅读了一些以有效时间复杂度计算斐波那契的方法,以下是其中的一些 -

    方法 1 - 动态规划 现在这里的子结构是众所周知的,因此我将直接跳到解决方案 -

    static int fib(int n) 
    { 
    int f[] = new int[n+2]; // 1 extra to handle case, n = 0 
    int i; 
    
    f[0] = 0; 
    f[1] = 1; 
    
    for (i = 2; i <= n; i++) 
    { 
        f[i] = f[i-1] + f[i-2]; 
    } 
    
    return f[n]; 
    }
    

    上面的空间优化版本可以按如下方式完成 -

    static int fib(int n) 
     { 
        int a = 0, b = 1, c; 
        if (n == 0) 
            return a; 
        for (int i = 2; i <= n; i++) 
        { 
            c = a + b; 
            a = b; 
            b = c; 
        } 
        return b; 
    } 
    

    方法2-(利用矩阵{{1,1},{1,0}}的幂)

    这是一个 O(n) 依赖于这样一个事实:如果我们将矩阵 M = {{1,1},{1,0}} 乘以 n 次(换句话说,计算 power(M, n ) ),然后我们得到第 (n+1) 个斐波那契数作为结果矩阵中行和列 (0, 0) 处的元素。这个解决方案需要 O(n) 时间。

    矩阵表示给出斐波那契数的以下封闭表达式: 斐波那契矩阵

    static int fib(int n) 
    { 
    int F[][] = new int[][]{{1,1},{1,0}}; 
    if (n == 0) 
        return 0; 
    power(F, n-1); 
    
    return F[0][0]; 
    } 
    
    /*multiplies 2 matrices F and M of size 2*2, and 
    puts the multiplication result back to F[][] */
    static void multiply(int F[][], int M[][]) 
    { 
    int x = F[0][0]*M[0][0] + F[0][1]*M[1][0]; 
    int y = F[0][0]*M[0][1] + F[0][1]*M[1][1]; 
    int z = F[1][0]*M[0][0] + F[1][1]*M[1][0]; 
    int w = F[1][0]*M[0][1] + F[1][1]*M[1][1]; 
    
    F[0][0] = x; 
    F[0][1] = y; 
    F[1][0] = z; 
    F[1][1] = w; 
    } 
    
    /*function that calculates F[][] raise to the power n and puts the 
    result in F[][]*/
    static void power(int F[][], int n) 
    { 
    int i; 
    int M[][] = new int[][]{{1,1},{1,0}}; 
    
    // n - 1 times multiply the matrix to {{1,0},{0,1}} 
    for (i = 2; i <= n; i++) 
        multiply(F, M); 
    } 
    

    这可以优化为在 O(Logn) 时间复杂度下工作。我们可以在前面的方法中进行递归乘法得到power(M, n)。

    static int fib(int n) 
    { 
    int F[][] = new int[][]{{1,1},{1,0}}; 
    if (n == 0) 
        return 0; 
    power(F, n-1); 
    
    return F[0][0]; 
    } 
    
    static void multiply(int F[][], int M[][]) 
    { 
    int x =  F[0][0]*M[0][0] + F[0][1]*M[1][0]; 
    int y =  F[0][0]*M[0][1] + F[0][1]*M[1][1]; 
    int z =  F[1][0]*M[0][0] + F[1][1]*M[1][0]; 
    int w =  F[1][0]*M[0][1] + F[1][1]*M[1][1]; 
    
    F[0][0] = x; 
    F[0][1] = y; 
    F[1][0] = z; 
    F[1][1] = w; 
    } 
    
    static void power(int F[][], int n) 
    { 
    if( n == 0 || n == 1) 
      return; 
    int M[][] = new int[][]{{1,1},{1,0}}; 
    
    power(F, n/2); 
    multiply(F, F); 
    
    if (n%2 != 0) 
       multiply(F, M); 
    } 
    

    方法3(O(log n)时间) 下面是一个更有趣的递归公式,可用于在 O(log n) 时间内找到第 n 个斐波那契数。

    如果 n 为偶数,则 k = n/2: F(n) = [2*F(k-1) + F(k)]*F(k)

    如果 n 是奇数,则 k = (n + 1)/2 F(n) = F(k)*F(k) + F(k-1)*F(k-1) 这个公式是如何工作的? 该公式可以从上述矩阵方程推导出来。 斐波那契矩阵

    取两边的行列式,我们得到 (-1)n = Fn+1Fn-1 – Fn2 此外,由于任意方阵 A 的 AnAm = An+m,因此可以推导出以下恒等式(它们是从矩阵乘积的两个不同系数中获得的)

    FmFn + Fm-1Fn-1 = Fm+n-1

    通过设置 n = n+1,

    FmFn+1 + Fm-1Fn = Fm+n

    设 m = n

    F2n-1 = Fn2 + Fn-12

    F2n = (Fn-1 + Fn+1)Fn = (2Fn-1 + Fn)Fn(来源:Wiki)

    要得到要证明的公式,我们只需要执行以下操作 如果 n 是偶数,我们可以设 k = n/2 如果 n 是奇数,我们可以设 k = (n+1)/2

    public static int fib(int n) 
    { 
    
        if (n == 0) 
            return 0; 
    
        if (n == 1 || n == 2) 
            return (f[n] = 1); 
    
        // If fib(n) is already computed 
        if (f[n] != 0) 
            return f[n]; 
    
        int k = (n & 1) == 1? (n + 1) / 2 
                            : n / 2; 
    
        // Applyting above formula [See value 
        // n&1 is 1 if n is odd, else 0. 
        f[n] = (n & 1) == 1? (fib(k) * fib(k) +  
                        fib(k - 1) * fib(k - 1)) 
                       : (2 * fib(k - 1) + fib(k))  
                       * fib(k); 
    
        return f[n]; 
    } 
    

    方法 4 - 使用公式 在这种方法中,我们直接实现了斐波那契数列中第 n 项的公式。时间 O(1) 空间 O(1) Fn = {[(√5 + 1)/2] ^ n} / √5

    static int fib(int n) { 
    double phi = (1 + Math.sqrt(5)) / 2; 
    return (int) Math.round(Math.pow(phi, n)  
                        / Math.sqrt(5)); 
    } 
    

    参考:http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibFormula.html

    【讨论】:

      【解决方案3】:

      编辑:我实际上认为 Hynek Vychodil 的答案比我的要好,但我将其留在这里以防万一有人正在寻找替代方法。

      我认为其他方法都是有效的,但不是最优的。原则上使用 Binet 公式应该会给您正确的答案,但是对于较大的 n 值,四舍五入到最接近的整数会产生一些问题。其他解决方案会在您每次调用函数时不必要地重新计算最多 n 的值,因此该函数未针对重复调用进行优化。

      在我看来,最好的办法是定义一个全局数组,然后在需要时向数组添加新值。在 Python 中:

      import numpy
      
      fibo=numpy.array([1,1])
      last_index=fibo.size
      
      def fib(n):
          global fibo,last_index
          if (n>0):
              if(n>last_index):
                  for i in range(last_index+1,n+1):
                      fibo=numpy.concatenate((fibo,numpy.array([fibo[i-2]+fibo[i-3]])))
                  last_index=fibo.size
              return fibo[n-1]
          else:
              print "fib called for index less than 1"
              quit()
      

      当然,如果您需要为 n>80(大约)调用 fib,那么您将需要实现任意精度的整数,这在 python 中很容易做到。

      【讨论】:

        【解决方案4】:
        // D Programming Language 
        
        void vFibonacci ( const ulong X, const ulong Y, const int Limit ) {    
               // Equivalent : if ( Limit != 10 ). Former ( Limit ^ 0xA ) is More Efficient However.
               if ( Limit ^ 0xA ) {    
                   write ( Y, " " ) ;
                   vFibonacci ( Y, Y + X, Limit + 1 ) ;
               } ;
        } ;
        
        // Call As    
        // By Default the Limit is 10 Numbers
        vFibonacci ( 0, 1, 0 ) ;
        

        【讨论】:

        • 一些通俗易懂的代码解释几乎总是有利于答案的清晰。
        【解决方案5】:

        在此处查找使用公式的implementation in Erlang .它显示了很好的线性结果行为,因为在O(M(n) log n) 部分M(n) 是大数字的指数。它在 2 秒内计算出一百万的 fib,其中结果有 208988 位。诀窍是您可以使用(尾)递归公式计算O(log n) 乘法中的幂(尾表示使用适当的编译器或重写循环时使用O(1) 空间):

        % compute X^N
        power(X, N) when is_integer(N), N >= 0 ->
            power(N, X, 1).
        
        power(0, _, Acc) ->
            Acc;
        power(N, X, Acc) ->
            if N rem 2 =:= 1 ->
                    power(N - 1,   X,     Acc * X);
                true ->
                    power(N div 2, X * X, Acc)
            end.
        

        XAcc 你用矩阵代替。 X 将以 和Acc 启动,身份I 等于。

        【讨论】:

          【解决方案6】:

          您可以保存结果并使用它们:

          public static long[] fibs;
          
          public long fib(int n) {
              fibs = new long[n];
              return internalFib(n);
          }
          public long internalFib(int n) {
              if (n<=2) return 1;
              fibs[n-1] = fibs[n-1]==0 ? internalFib(n-1) : fibs[n-1];
              fibs[n-2] = fibs[n-2]==0 ? internalFib(n-2) : fibs[n-2];
              return fibs[n-1]+fibs[n-2];
          }
          

          【讨论】:

            【解决方案7】:

            F(n) = (φ^n)/√5 并四舍五入到最接近的整数,其中 φ 是黄金分割率......

            φ^n 可以在 O(lg(n)) 时间内计算,因此 F(n) 可以在 O(lg(n)) 时间内计算。

            【讨论】:

              【解决方案8】:

              提示:获得更快结果的一种方法是使用Binet's formula

              这是一种在 Python 中实现的方法:

              from decimal import *
              
              def fib(n):
                  return int((Decimal(1.6180339)**Decimal(n)-Decimal(-0.6180339)**Decimal(n))/Decimal(2.236067977))
              

              【讨论】:

              • Binet 的公式如您所介绍的那样对于大 n 是不准确的,因为您的常量中的舍入误差为 1.6180339。请参阅我的 answer 以快速准确地计算 big n
              【解决方案9】:

              一种简单的方法是迭代计算而不是递归计算。这将在线性时间内计算 F(n)。

              def fib(n):
                  a,b = 0,1
                  for i in range(n):
                      a,b = a+b,a
                  return a
              

              【讨论】:

              • 有一种方法可以在O(M(n)log n) 中计算,其中M(n) 是乘法成本。对于大数字,您的代码实际上是 O(n^2)。请参阅我的 answer 以快速准确地计算 big n。无论如何,您必须使用一些具有此支持的大数字库或语言。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-12-18
              • 2015-04-25
              • 1970-01-01
              • 2017-11-13
              • 2013-05-04
              • 2020-01-18
              • 1970-01-01
              相关资源
              最近更新 更多