亚线性时间中的第n个斐波那契数


76

是否有任何算法可以在亚线性时间内计算第n个斐波那契数?


4
有人可能会争辩说,它涉及到的算法,因为OP作出含糊的提法,以算法的复杂性......我还是很好奇什么算法虽然。
马修·沙利

2
下面的两个答案具有正确的公式。关于这个问题是否与编程有关:它是计算机科学的一部分。用于导出公式的设备称为“生成函数”,在算法分析中具有重要作用。
azheglov

1
@azheglov:虽然生成函数很有用,但不需要为斐波那契序列导出封闭形式的表达式。
杰森

7
您有一个无论出于何种原因都想解决的问题,并且想要高效地解决。有时,所需的见识将是新的实现,有时是算法,有时是数学。无需在每次发生“与编程无关”的情况时就予以谴责。
ShreevatsaR

7
结果的大小在n中是线性的。因此,没有这样的算法。当然,这不会使下面使用O(log n)算术运算来计算斐波那契数的任何好的答案都无效。
ip科2009年

Answers:


66

所述n第Fibonacci数由下式给出

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

哪里

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

假设原始数学运算(+-*/)是O(1)可以使用该结果来计算n在第Fibonacci数O(log n)时间(O(log n)因为式中的求幂的)。

在C#中:

static double inverseSqrt5 = 1 / Math.Sqrt(5);
static double phi = (1 + Math.Sqrt(5)) / 2;
/* should use 
   const double inverseSqrt5 = 0.44721359549995793928183473374626
   const double phi = 1.6180339887498948482045868343656
*/

static int Fibonacci(int n) {
    return (int)Math.Floor(Math.Pow(phi, n) * inverseSqrt5 + 0.5);
}

7
@Json我没有打败您,但其他人可能会这样做,因为您的回答表明可以在O(log n)时间计算第N个斐波那契数,这是错误的。您的代码正在计算一个近似值。您的代码的任意精度至少应为O(n),因为答案的长度为O(n)。
PeterAllenWebb

10
@PeterAllenWebb:提供的公式不是一个近似值。第n个斐波纳契数等于phi^n / sqrt(5) + 1/2where的底数phi = (1 + sqrt(5)) / 2。这是事实。其次,我理解其他人对答案长度的O(n)看法,但是我在答案中加了一个注解,假设原始数学运算花费的时间是恒定的(除非绑定输入,否则我不会知道)。我的观点是,我们可以在O(log n)算术运算中找到第n个斐波那契数。
杰森

4
@Jason:假设幂也为O(1),则整个算法也为O(1)。那样很好,但是,求幂不是O(1),其他原始数学运算也不是。简而言之,该公式很好,但是它不会以亚线性时间计算结果。
yairchu

12
@Jason:公式不是一个近似值,但是代码是一个近似值(在虚构的C#实现中,其中Math.Pow(...)具有无限精度,在这种情况下,代码为O(n))。
ShreevatsaR 2011年

14
@杰森:不。在n = 1000上运行您的代码(斐波那契数字43466 ... 849228875的位数仅为209位),并告诉我是否正确使用所有数字。为了使Math.Floor正确获得整数部分,必须由Math.Pow准确计算出许多数字。实际上,在我的C ++实现中,即使16位数字F_ {74} = 130496954492865的计算也是错误的,即使整数130496954492865可以精确地表示(很长很长),如果C#获得更多数字,我会感到惊讶比起那个来说。
ShreevatsaR 2011年

100

遵循Pillsy对矩阵乘幂的引用,例如对于矩阵

M = [1 1]
    [1 0] 

然后

fibn)= M n 1,2

使用重复乘法将矩阵乘幂不是很有效。

矩阵求幂的两种方法是分而治之,即以Oln n)步长产生M n,或者是固定时间的特征值分解,但由于有限的浮点精度而可能引入误差。

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

M n =(M n / 22如果n个偶数
   = M · M n -1如果n为奇数

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

 中号  = û  Λ  ù -1 
 中号Ñ =(û  Λ  Ú -1ñ 
    = Ü  Λ  ù -1  ù  Λ  ù -1  ù  Λ  ù -1 ... n次
    = ü  Λ  Λ  Λ ... ü -1  
    = ü  Λ  ñ  ü -1 
养对角矩阵ΛÑ次方是在提高各个元件的一个简单的事情 ΛÑ个,所以这给出了提高的O(1)方法中号Ñ次方。但是,Λ中的值不太可能是整数,因此会发生一些错误。

定义Λ2x2矩阵的为

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

找出每个λ,我们求解

| 中号- λ| = 0

这使

| 中号- λ| =-λ(1-λ)-1

λ²-λ-1 = 0

使用二次公式

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

如果您已经阅读了Jason的答案,那么可以看到它的去向。

求解特征向量X 1X 2

如果X 1 = [ X 1,1X 1,2 ]

 中号X 1 11 X 1

 X 1,1 + X 1,21  X 1,1 
 X 1,11  X 1,2

=>
 X 1 = [Φ,1]
  X 2 = [1-Φ,1]

这些向量给U

U = [ X 1,1X 2,2 ]
    [ X 1,1X 2,2 ]

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

倒相 使用U

一种    = [ab]
      [cd]
=>
A -1 =(1 / | A |)[d -b]
                   [-ca]

所以U -1

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

完整性检查:

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

令Ψ=1-Φ,另一个特征值

因为Φ是λ²-λ-1= 0的根 
所以-ΨΦ=Φ²-Φ= 1
Ψ+Φ= 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所需的一切:

中号Ñ = û Λ Ñ ù -1 
   =(√5)-1 [ΦΨ]。[Φ Ñ   0]。[1-Ψ]
              [1 1] [0Ψ Ñ ] [-1Φ]

   =(√5)-1 [ΦΨ]。[Φ Ñ   -ΨΦ Ñ ]
              [11] [-Ψ Ñ    Ψ Ñ Φ]

   =(√5)-1 [ΦΨ]。[Φ Ñ    Φ Ñ -1 ]
              [11] [-Ψ ÑÑ -1 ]作为ΨΦ= -1

   =(√5)-1Ñ 1Ñ 1       Φ ÑÑ ]
              [Φ ÑÑ       Φ Ñ -1Ñ -1 ]

所以

 FIBÑ)=中号Ñ 1,2 
        =(Φ ñ - (1-Φ)ñ)/√5

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

您可以从递归关系中得出它,但是在工程计算和仿真中,计算大型矩阵的特征值和特征向量是一项重要的活动,因为它可以提供方程组系统的稳定性和谐波,并允许将矩阵有效地提高到高幂。


+1-和往常一样很棒。您用什么排版了?胶乳?
duffymo

它是从Gilbert Strang的代数书或线性代数的其他好书中复制粘贴的。
2014年

1
@alinsoar并不是“复制粘贴”,而是作为一种练习来检查我是否还记得我的语言,并参考了开放大学的课程笔记和维基百科。
Pete Kirkham 2014年

我和吉尔伯特·斯特朗(Gilbert Strang)一起学习了L代数课程,那里是一样的。通过矩阵分解表示递归的问题很经典,可以在任何好的教科书/课程中找到。
2014年

56

如果您想要确切的数字(是“ bignum”,而不是int / float),那么恐怕

不可能!

如上所述,斐波那契数的公式是:

fib n =底数(phi n /√5+ 1 / 2

fib n〜= phi n /√5

几位数是fib n多少?

numDigits(fib n)=日志(fib n)=日志(phi n /√5)=日志phi n-日志√5= n *日志phi-日志√5

numDigits(fib n)= n * const + const

On

由于请求的结果为On),因此无法在少于On)的时间内进行计算。

如果只希望答案的低位数字,则可以使用矩阵求幂方法在亚线性时间内进行计算。


2
@yairchu:如果我理解正确的话,让我改一下。理论上,计算fib_n需要计算n位数字,因此,对于任意n,将花费O(n)时间。但是,如果fib_n <sizeof(long long),则由于机器体系结构提供了一种设置位的并行机制,因此我们可以在O(log n)时间中计算fib_n。(例如,INT I = -1;需要设置32位,但在32位机器上的所有32个比特可以在恒定的时间进行设置。
萨米特

7
@Sumit:如果您只想支持适合32位的结果,那么您也可以为该系列的前48个结果提供一个查找表。显然,这是O(1),但是:对有界N进行大O分析是很愚蠢的,因为您始终可以将任何内容合并到常数因子中。所以我的答案是指无限制的输入。
yairchu

1
@yairchu:您能否说明一个著名示例的逻辑,例如O(n*log n)基于比较的n数字序列排序,其中每个数字都有O(log n)数字?
jfs 2014年

1
是对还是错,取决于您打算“时间”的含义。对于排序(或哈希表查找),“时间”表示比较的次数。在这个问题上可能意味着算术运算。在此答案中,其含义是类似于数字运算。
Paul Hankin

4
整数在基数sqrt(2)中确实具有有限的表示形式,但是在奇数位上它只会为零,即等于基数2。如果基数sqrt(2)中的任何奇数位都为非零,则您的数字是无理数。将连续信号转换为模拟信号时,可能需要在ADC中使用基本phi。Afaik,这是基础phi的“工业”应用,用于在对信号进行四舍五入时减少粗糙的纹理。不过,就我个人而言,我使用基本phi和fibonacci编码作为与辫子组的Fibonacci anyon表示形式一起使用的一种方便的符号表示方式。
saolof

34

SICP中的一项练习就是关于此的,这里有答案

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

功能 Fib计数a ←1
     b ←0
     p ←0
     q ←1

     计数> 0待办事项
        如果即使(计数。然后
             pp 2 + q ²
              q ←2 PQ + q ²
             计数计数÷2
        否则
             一个BQ +水溶液+ AP 
             bBP +水溶液
             计数计数- 1
        结束如果
    结束时间

    Return  b
结束功能

1
计数尚未初始化
yairchu

这是Python中实现(与twisted框架一起使用)。
jfs 2013年

“ If If(count)Then”应为“ If Odd(count)那么”
Monirul Islam Milon

@MonirulIslamMilonif even(count)是正确的。序列从零开始(零斐波那契数为零):0,1,1,2,3,5,8,13,...
jfs


24

您也可以通过对整数矩阵求幂来实现。如果你有矩阵

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

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

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

第二次编辑:首先使用特征分解对矩阵进行指数运算,这完全等效于下面的JDunkerly解决方案。此矩阵的特征值是(1 + sqrt(5))/2(1 - sqrt(5))/2


3
使用M的特征分解有效地计算M ^ n。
Pete Kirkham

1
建议的方法适用于整数计算(可能使用长算术)。本征分解的方法并不有趣:如果不需要整数计算,请使用Jason答案中的公式。
康斯坦丁·丹增2009年

1
@Konstantin Jason答案的公式是特征分解给出的结果,因此您与自己矛盾。
皮特·柯坎

@Pete Kirkham该公式可以通过几种方法获得:特征方程,特征分解,归纳证明。我不确定本征分解是最简单的。无论如何,它都是众所周知的,并且更容易立即使用
康斯坦丁·丹增2009年

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;
    }

2
通过使用|1 - phi|^n / sqrt(5) < 1/2whenn是非负整数这一事实,可以避免计算两个指数的需要。
杰森

不知道调整总是使用其他形式,但这是一个不错的优化
JDunkerley 2009年

1
近似结果的正确解包括矩阵乘法。
cerkiewny14年

4

对于真正的大对象,此递归函数有效。它使用以下方程式:

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个斐波那契数等。


这段代码效率不高。想象一下,fibs []数组的大小仅为10,您调用了Fib(101)。Fib(101)称为Fib(51)和Fib(50)。Fib(51)称为Fib(26)和Fib(25)。Fib(50)称为Fib(25)和Fib(24)。因此,Fib(25)被叫了两次,这是浪费。即使最多有500个fib,Fib(100000)也会有相同的问题。
2014年

3

这是我的递归版本,它递归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)]

简化右侧会导致偶数情况。


在这里我要强调的是,您要根据F(n)和F(n-1)的函数来计算F(2n)和F(2n + 1)。您没有指出要做什么。
alinsoar 2014年

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

1

定点运算不准确。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


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位OS)花费了大约0.7秒


但是,这是线性时间,该问题专门询问如何实现亚线性时间(使用某种封闭形式的解决方案可以实现)。
罗密欧·瓦伦丁


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

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)* phi。

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

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])

请注意,此代码的大部分是标准的平方乘幂运算。

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


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(资料来源:维基)

为了证明该公式,我们只需要执行以下操作:如果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-使用公式 在此方法中,我们直接实现Fibonacci级数中第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


0

我们首先应该注意到,斐波那契数(F(n))随着增长很快,n并且对于大于93的数,它不能用64位表示。nn需要使用附加的机制来处理这些大数。现在,仅考虑(大量)操作的计数,按顺序计算它们的算法将需要线性操作数。

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

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和可能加1 x,我们也可以计算的新的值F(x)使用的较早的值F(x)F(x+1)与上述方程。

由于迭代次数将为对数n,因此总的(大数)运算也将为对数n

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.