次线性时间的第n个斐波纳契数

时间:2009-10-06 13:16:49

标签: performance algorithm math time-complexity fibonacci

有没有算法在子线性时间内计算第n个斐波纳契数?

16 个答案:

答案 0 :(得分:97)

根据Pillsy对矩阵求幂的引用,对于矩阵

M = [1 1] 
    [1 0] 

然后

fib(n) = Mn1,2

使用重复乘法将矩阵提升到幂并不是非常有效。

矩阵求幂的两种方法是分而治之,它在 O ln n )中产生 M n 步骤,或特征值分解是恒定时间,但可能由于有限的浮点精度而引入误差。

如果您希望精确值大于浮点实现的精度,则必须使用基于此关系的O(ln n)方法:

Mn = (Mn/2)2 if n even
   = M·Mn-1 if n is odd

M 上的特征值分解找到两个矩阵 U Λ,使得Λ是对角线 p>

 M  = U Λ U-1 
 Mn = ( U Λ U-1) n
    = U Λ U-1 U Λ U-1 U Λ U-1 ... n times
    = U Λ Λ Λ ... U-1 
    = U Λ n U-1 
将对角矩阵Λ提升到 n次幂是将Λ中的每个元素提升到 n th,因此这给出了将 M 提高到 n次幂的O(1)方法。但是,Λ中的值不可能是整数,因此会出现一些错误。

为我们的2x2矩阵定义Λ

Λ = [ λ1 0 ]
  = [ 0 λ2 ]

要找到每个λ,我们解决

 |M - λI| = 0

给出了

 |M - λI| = -λ ( 1 - λ ) - 1

λ² - λ - 1 = 0

使用二次公式

λ    = ( -b ± √ ( b² - 4ac ) ) / 2a
     = ( 1 ± √5 ) / 2
 { λ1, λ2 } = { Φ, 1-Φ } where Φ = ( 1 + √5 ) / 2

如果您已经阅读了Jason的回答,您可以看到这将会发生什么。

求解特征向量 X 1 X 2

if X1 = [ X1,1, X1,2 ]

 M.X1 1 = λ1X1

 X1,1 + X1,2 = λ1 X1,1
 X1,1      = λ1 X1,2

=>
 X1 = [ Φ,   1 ]
 X2 = [ 1-Φ, 1 ]

这些载体给出 U

U = [ X1,1, X2,2 ]
    [ X1,1, X2,2 ]

  = [ Φ,   1-Φ ]
    [ 1,   1   ]

使用

反转 U
A   = [  a   b ]
      [  c   d ]
=>
A-1 = ( 1 / |A| )  [  d  -b ]
                   [ -c   a ]

所以 U -1

给出
U-1 = ( 1 / ( Φ - ( 1 - Φ ) )  [  1  Φ-1 ]
                               [ -1   Φ  ]
U-1 = ( √5 )-1  [  1  Φ-1 ]
               [ -1   Φ  ]

完整性检查:

UΛU-1 = ( √5 )-1 [ Φ   1-Φ ] . [ Φ   0 ] . [ 1  Φ-1 ] 
                     [ 1   1  ]   [ 0  1-Φ ]   [ -1   Φ ]

let Ψ = 1-Φ, the other eigenvalue

as Φ is a root of λ²-λ-1=0 
so  -ΨΦ = Φ²-Φ = 1
and Ψ+Φ = 1

UΛU-1 = ( √5 )-1 [ Φ   Ψ ] . [ Φ   0 ] . [  1  -Ψ ] 
                 [ 1   1 ]   [ 0   Ψ ]   [ -1   Φ ]

       = ( √5 )-1 [ Φ   Ψ ] . [ Φ   -ΨΦ ] 
                 [ 1   1 ]   [ -Ψ  ΨΦ ]

       = ( √5 )-1 [ Φ   Ψ ] . [ Φ    1 ] 
                 [ 1   1 ]   [ -Ψ  -1 ]

       = ( √5 )-1 [ Φ²-Ψ²  Φ-Ψ ] 
                  [ Φ-Ψ      0 ]

       = [ Φ+Ψ   1 ]    
         [ 1     0 ]

       = [ 1     1 ] 
         [ 1     0 ]

       = M 

因此,理智检查成立。

现在我们有了计算所需的一切 M n 1,2

Mn = UΛnU-1
   = ( √5 )-1 [ Φ   Ψ ] . [ Φn  0 ] . [  1  -Ψ ] 
              [ 1   1 ]   [ 0   Ψn ]   [ -1   Φ ]

   = ( √5 )-1 [ Φ   Ψ ] . [  Φn  -ΨΦn ] 
              [ 1   1 ]   [ -Ψn   ΨnΦ ]

   = ( √5 )-1 [ Φ   Ψ ] . [  Φn   Φn-1 ] 
              [ 1   1 ]   [ -Ψnn-1 ] as ΨΦ = -1

   = ( √5 )-1 [ Φn+1n+1      Φnn ]
              [ Φnn      Φn-1n-1 ]

所以

 fib(n) = Mn1,2
        = ( Φn - (1-Φ)n ) / √5

这与其他地方给出的公式一致。

您可以从重复关系推导出它,但在工程计算和模拟中计算大矩阵的特征值和特征向量是一项重要的活动,因为它给出了方程组的稳定性和谐波,并允许将矩阵提高到高有效率。

答案 1 :(得分:64)

n斐波纳契数由

给出
f(n) = Floor(phi^n / sqrt(5) + 1/2) 

其中

phi = (1 + sqrt(5)) / 2

假设原始数学运算(+-*/)为O(1),您可以使用此结果来计算{{ 1}} n时间内的斐波纳契数(O(log n),因为公式中取幂)。

在C#中:

O(log n)

答案 2 :(得分:54)

如果你想要确切的数字(这是一个“bignum”,而不是一个int / float),那我恐怕

这是不可能的!

如上所述,斐波纳契数的公式为:

  

fib n = floor(phi n /√5+ 1 / 2

     

fib n~ = phi n /√5

fib n有多少位?

  

numDigits(fib n)= log(fib n)= log(phi n /√5)= log phi n - log√5= n * log phi - 记录√5

     

numDigits(fib n)= n * const + const

     

它是 O n

由于请求的结果是 O n ),因此无法以小于 O n)计算)时间。

如果您只想要答案的低位数,那么可以使用矩阵求幂方法在子线性时间内计算。

答案 3 :(得分:33)

其中一个exercises in SICP与此有关,其答案为here.

在命令式样式中,程序看起来像

Function Fib(count)
    a ← 1
    b ← 0
    p ← 0
    q ← 1

    While count > 0 Do
        If Even(count) Then
             pp² + q²
             q ← 2pq + q²
             countcount ÷ 2
        Else
             abq + aq + ap
             bbp + aq
             countcount - 1
        End If
    End While

    Return b
End Function

答案 4 :(得分:24)

您也可以通过对整数矩阵进行取幂来实现。如果您有矩阵

    / 1  1 \
M = |      |
    \ 1  0 /

然后(M^n)[1, 2]将等于n斐波纳契数,如果[]是矩阵下标且^是矩阵求幂。对于固定大小的矩阵,可以在O(log n)时间内以与实数相同的方式对正整数幂进行取幂。

编辑:当然,根据您想要的答案类型,您可以使用恒定时间算法。与其他公式显示的一样,n斐波那契数随着n呈指数增长。即使使用64位无符号整数,您也只需要一个94条目的查找表来覆盖整个范围。

第二次编辑:首先使用特征分解执行矩阵指数与下面的JDunkerly解决方案完全相同。该矩阵的特征值为(1 + sqrt(5))/2(1 - sqrt(5))/2

答案 5 :(得分:5)

维基百科有一个封闭的表格解决方案 http://en.wikipedia.org/wiki/Fibonacci_number

或者在c#中:

    public static int Fibonacci(int N)
    {
        double sqrt5 = Math.Sqrt(5);
        double phi = (1 + sqrt5) / 2.0;
        double fn = (Math.Pow(phi, N) - Math.Pow(1 - phi, N)) / sqrt5;
        return (int)fn;
    }

答案 6 :(得分:3)

对于非常大的,这个递归函数有效。它使用以下等式:

F(2n-1) = F(n-1)^2 + F(n)^2
F(2n) = (2*F(n-1) + F(n)) * F(n)

您需要一个可以使用大整数的库。我使用https://mattmccutchen.net/bigint/中的BigInteger库。

从一系列斐波那契数字开始。使用fibs [0] = 0,fibs [1] = 1,fibs [2] = 1,fibs [3] = 2,fibs [4] = 3等。在这个例子中,我使用第一个501的数组(数0)。你可以在这里找到前500个非零斐波纳契数:http://home.hiwaay.net/~jalison/Fib500.html。它需要一些编辑才能以正确的格式进行,但这并不太难。

然后你可以使用这个函数找到任何斐波纳契数(在C中):

BigUnsigned GetFib(int numfib)
{
int n;
BigUnsigned x, y, fib;  

if (numfib < 501) // Just get the Fibonacci number from the fibs array
    {
       fib=(stringToBigUnsigned(fibs[numfib]));
    }
else if (numfib%2) // numfib is odd
    {
       n=(numfib+1)/2;
       x=GetFib(n-1);
       y=GetFib(n);
       fib=((x*x)+(y*y));
    }
else // numfib is even
    {
       n=numfib/2;
       x=GetFib(n-1);
       y=GetFib(n);
       fib=(((big2*x)+y)*y);
   }
return(fib);
}

我已经测试了第25,000个Fibonacci数字等。

答案 7 :(得分:3)

这是我的recurses log(n)次的递归版本。我认为以递归形式阅读最简单:

def my_fib(x):
  if x < 2:
    return x
  else:
    return my_fib_helper(x)[0]

def my_fib_helper(x):
  if x == 1:
    return (1, 0)
  if x % 2 == 1:
    (p,q) = my_fib_helper(x-1)
    return (p+q,p)
  else:
    (p,q) = my_fib_helper(x/2)
    return (p*p+2*p*q,p*p+q*q)

它的工作原理是因为如果n是奇数,你可以使用fib(n),fib(n-1)计算fib(n-1),fib(n-2),如果n是偶数,你可以使用fib(n),fib(n-1)计算fib(n/2),fib(n/2-1)

基本案例和奇怪案例很简单。为了得到偶数情况,从a,b,c开始作为连续的斐波那契值(例如,8,5,3)并将它们写在矩阵中,其中a = b + c。注意:

[1 1] * [a b]  =  [a+b a]
[1 0]   [b c]     [a   b]
从那以后,我们看到前三个斐波纳契数的矩阵乘以任意三个连续斐波纳契数的矩阵,等于下一个。所以我们知道:

      n
[1 1]   =  [fib(n+1) fib(n)  ]
[1 0]      [fib(n)   fib(n-1)]

所以:

      2n                        2
[1 1]    =  [fib(n+1) fib(n)  ]
[1 0]       [fib(n)   fib(n-1)]

简化右侧导致偶数情况。

答案 8 :(得分:1)

使用R

l1 <- (1+sqrt(5))/2
l2 <- (1-sqrt(5))/2

P <- matrix(c(0,1,1,0),nrow=2) #permutation matrix
S <- matrix(c(l1,1,l2,1),nrow=2)
L <- matrix(c(l1,0,0,l2),nrow=2)
C <- c(-1/(l2-l1),1/(l2-l1))

k<-20 ; (S %*% L^k %*% C)[2]
[1] 6765

答案 9 :(得分:1)

除了通过数学方法进行微调之外,最佳的最佳解决方案之一(我认为)是使用字典以避免重复计算。

import time

_dict = {1:1, 2:1}

def F(n, _dict):
    if n in _dict.keys():
        return _dict[n]
    else:
        result = F(n-1, _dict) + F(n-2, _dict)
        _dict.update({n:result})
        return result

start = time.time()

for n in range(1,100000):
    result = F(n, _dict) 

finish = time.time()

print(str(finish - start))

我们从平凡的字典(斐波那契数列的前两个值)开始,不断将斐波那契值添加到字典中。

前100000个斐波那契值(2.70 GHz时的Intel Xeon CPU E5-2680 @ 2.70 GHz,16 GB RAM,Windows 10-64位操作系统)花费了大约0.7秒

答案 10 :(得分:0)

查看分而治之算法here

该链接具有针对此问题的其他一些答案中提到的矩阵求幂的伪代码。

答案 11 :(得分:0)

定点运算不准确。 Jason的C#代码给出了n = 71(308061521170130而不是308061521170129)及其后的错误答案。

要获得正确答案,请使用计算代数系统。 Sympy就是这样一个Python库。在http://live.sympy.org/有一个交互式控制台。复制并粘贴此功能

phi = (1 + sqrt(5)) / 2
def f(n):
    return floor(phi**n / sqrt(5) + 1/2)

然后计算

>>> f(10)
55

>>> f(71)
308061521170129

您可能想尝试检查phi

答案 12 :(得分:0)

你可以使用奇怪的平方根方程来得到一个确切的答案。原因是$ \ sqrt(5)$最后会掉落,你只需要用自己的乘法格式跟踪系数。

def rootiply(a1,b1,a2,b2,c):
    ''' multipy a1+b1*sqrt(c) and a2+b2*sqrt(c)... return a,b'''
    return a1*a2 + b1*b2*c, a1*b2 + a2*b1

def rootipower(a,b,c,n):
    ''' raise a + b * sqrt(c) to the nth power... returns the new a,b and c of the result in the same format'''
    ar,br = 1,0
    while n != 0:
        if n%2:
            ar,br = rootiply(ar,br,a,b,c)
        a,b = rootiply(a,b,a,b,c)
        n /= 2
    return ar,br

def fib(k):
    ''' the kth fibonacci number'''
    a1,b1 = rootipower(1,1,5,k)
    a2,b2 = rootipower(1,-1,5,k)
    a = a1-a2
    b = b1-b2
    a,b = rootiply(0,1,a,b,5)
    # b should be 0!
    assert b == 0
    return a/2**k/5

if __name__ == "__main__":
    assert rootipower(1,2,3,3) == (37,30) # 1+2sqrt(3) **3 => 13 + 4sqrt(3) => 39 + 30sqrt(3)
    assert fib(10)==55

答案 13 :(得分:0)

这是一个在O(log n)算术运算中使用大小为O(n)的整数计算F(n)的单行:

for i in range(1, 50):
    print(i, pow(2<<i, i, (4<<2*i)-(2<<i)-1)//(2<<i))

使用大小为O(n)的整数是合理的,因为这与答案的大小相当。

要理解这一点,让phi为黄金比率(x ^ 2 = x + 1的最大解),F(n)为第n个斐波那契数,其中F(0)= 0,F(1 )= F(2)= 1

现在,phi ^ n = F(n-1)+ F(n)phi。

  

通过诱导证明:phi ^ 1 = 0 + 1 * phi = F(0)+ F(1)phi。如果phi ^ n =   F(n-1)+ F(n)phi,则phi ^(n + 1)= F(n-1)phi + F(n)phi ^ 2 = F(n-1)phi +   F(n)(phi + 1)= F(n)+(F(n)+ F(n-1))phi = F(n)+ F(n + 1)phi。在这个计算中唯一棘手的步骤是用(1 + phi)代替phi ^ 2,因为phi是黄金比例。

形式(a + b * phi)的数字,其中a,b是整数,在乘法时是关闭的。

  

证明:(p0 + p1 * phi)(q0 + q1 * phi)= p0q0 +(p0q1 + q1p0)phi + p1q1 * phi ^ 2 =   p0q0 +(p0q1 + q1p0)phi + p1q1 *(phi + 1)=(p0q0 + p1q1)+   (p0q1 + q1p0 + p1q1)*披

使用这种表示,可以通过平方运算使用取幂来计算O(log n)整数运算中的phi ^ n。结果将是F(n-1)+ F(n)phi,从中可以读出第n个Fibonacci数。

def mul(p, q):
    return p[0]*q[0]+p[1]*q[1], p[0]*q[1]+p[1]*q[0]+p[1]*q[1]

def pow(p, n):
    r=1,0
    while n:
        if n&1: r=mul(r, p)
        p=mul(p, p)
        n=n>>1
    return r

for i in range(1, 50):
    print(i, pow((0, 1), i)[1])

请注意,此代码的大部分是标准取幂函数。

要获得开始此答案的单行,可以注意到用足够大的整数X表示phi,可以执行(a+b*phi)(c+d*phi)作为整数运算(a+bX)(c+dX) modulo (X^2-X-1)。然后pow函数可以被标准的Python pow函数替换(它方便地包含第三个参数z,它计算结果模zX选择的是2<<i

答案 14 :(得分:0)

以下是其中一些有效时间复杂度的斐波那契计算方法-

方法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),它依赖于以下事实:如果我们n次将矩阵M = {{{1,1},{1,0}}与其自身相乘(换句话说,计算power(M,n) ),然后获得第(n + 1)个斐波那契数作为结果矩阵中行和列(0,0)的元素。该解决方案将具有O(n)时间。

矩阵表示法给出了斐波那契数的以下闭合表达式: Fibonaccimatrix

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)时间复杂度工作。我们可以使用先前的方法进行递归乘法以获得幂(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) 这个公式如何运作? 该公式可以从上述矩阵方程式导出。 Fibonaccimatrix

考虑双方的决定因素,我们得到 (-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

答案 15 :(得分:0)

我们首先应注意,(F(n))的斐波那契数n增长非常快,对于大于93的n,不能用 64位表示。为此n计算它们的程序需要使用其他机制来处理这些大数。现在,仅考虑(大量)运算的数量,按顺序计算它们的算法将需要线性运算。

我们可以从以下有关斐波那契数的标识中受益:

F(2m) = 2*F(m)*F(m+1) − (F(m))^2

F(2m+1) = (F(m))^2 + (F(m+1))^2

(像A ^ 2这样的符号表示A的平方)。

因此,如果我们知道F(m)F(m+1),就可以直接计算F(2m)F(2m+1)

考虑n的二进制表示形式。请注意,从x = 1开始,我们可以通过将x = n迭代加倍并可能加1来制作x。这可以通过遍历n的位并检查它是0还是1来完成。

这个想法是,我们可以保持F(x)x同步。在每个这样的迭代中,当我们将x加倍并可能向x加1时,我们还可以使用F(x)和{{ 1}},具有上述等式。

由于迭代次数在F(x)中是对数的,所以总数(大)运算在F(x+1)中也是对数的。

有关更多详细信息,请参阅此article的“改进算法”部分。