Hui*_*eng 31
您可以通过使用memoization节省大量时间.例如,比较以下两个版本(在JavaScript中):
版本1:正常递归
var fib = function(n) {
return n < 2 ? n : fib(n - 1) + fib(n - 2);
};
Run Code Online (Sandbox Code Playgroud)
版本2:记忆
A.使用下划线库
var fib2 = _.memoize(function(n) {
return n < 2 ? n : fib2(n - 1) + fib2(n - 2);
});
Run Code Online (Sandbox Code Playgroud)
B.无图书馆
var fib3 = (function(){
var memo = {};
return function(n) {
if (memo[n]) {return memo[n];}
return memo[n] = (n <= 2) ? 1 : fib3(n-2) + fib3(n-1);
};
})();
Run Code Online (Sandbox Code Playgroud)
对于n = 50(在Chrome上),第一个版本需要3分钟,而第二个版本只需要不到5毫秒!你可以在jsFiddle中查看这个.
如果我们知道版本1的时间复杂度是指数(O(2 N/2)),而版本2是线性的(O(N)),那就不足为奇了.
版本3:矩阵乘法
此外,我们甚至可以通过计算N个矩阵的乘法将时间复杂度降低到O(log(N)).

其中F n表示Fibonacci序列的第n项.
pri*_*imo 18
我同意Wayne Rooney的答案,即最佳解决方案将在O(log n)步骤中完成,但总体运行时复杂性将取决于所使用的乘法算法的复杂性.例如,使用Karatsuba乘法,整体运行时复杂度将为O(n log 2 3)≈O(n 1.585).1
但是,矩阵求幂不一定是最好的方法.正如Project Nayuki的开发人员所注意到的那样,矩阵取幂带有冗余计算,可以将其删除.从作者的描述:
给定F k和F k + 1,我们可以计算出这些:
请注意,每次拆分只需要3次BigInt到BigInt的乘法运算,而不是8次矩阵取幂.
不过,我们仍然可以做得稍微好一些.最优雅的斐波那契身份之一与卢卡斯数字有关:
其中L n是第n 个 卢卡斯数.此身份可以进一步概括为:
有一些或多或少的等效方法可以递归地进行,但最合乎逻辑的似乎是F n和L n.下面的实现中使用的其他标识可以从Lucas序列列出的标识中找到或派生:
以这种方式进行每次拆分只需要两次BigInt-to-BigInt乘法,而最终结果只需要一次.这比Project Nayuki(测试脚本)提供的代码快20%左右.注意:原始来源已经过轻微修改(改进),以便进行公平比较.
def fibonacci(n):
def fib_inner(n):
'''Returns F[n] and L[n]'''
if n == 0:
return 0, 2
u, v = fib_inner(n >> 1)
q = (n & 2) - 1
u, v = u * v, v * v + 2*q
if (n & 1):
u1 = (u + v) >> 1
return u1, 2*u + u1
return u, v
u, v = fib_inner(n >> 1)
if (n & 1):
q = (n & 2) - 1
u1 = (u + v) >> 1
return v * u1 + q
return u * v
Run Code Online (Sandbox Code Playgroud)
一个灰熊指出,上述结果已经由Takahashi(2000)2改进,注意到BigInt平方通常(特别是Schönhage-Strassen算法)比BigInt乘法计算成本更低.作者建议迭代,使用以下标识拆分F n和L n:
以这种方式迭代将需要每次拆分两个BigInt方块,而不是如上所述的BigInt方形和BigInt乘法.正如预期的那样,对于非常大的n,运行时间明显快于上述实现,但对于小值(n <25000),运行时间稍慢.
但是,这也可以略微改善.作者声称,"众所周知,卢卡斯数的乘积算法使用最少的比特运算来计算斐波纳契数F n".然后,作者选择调整Lucas Numbers算法的产品,该算法在当时是最快的已知的,在F n和L n上分裂.但是请注意,L n仅用于计算F n + 1.如果考虑以下身份,这似乎有点浪费:
其中第一个是直接来自Takahashi,第二个是矩阵求幂方法的结果(也由Nayuki指出),第三个是加上前两个的结果.这允许在F n和F n + 1上进行明显的分割.结果需要少一个BigInt添加,并且重要的是,偶数n减少一个除以2 ; 对于奇数n,利益加倍.在实践中,这显着快于Takahashi提出的小n(快10-15%)的方法,对于非常大的n(测试脚本)略快.
def fibonacci(n):
def fib_inner(n):
'''Returns F[n] and F[n+1]'''
if n == 0:
return 0, 1
u, v = fib_inner(n >> 1)
q = (n & 2) - 1
u *= u
v *= v
if (n & 1):
return u + v, 3*v - 2*(u - q)
return 2*(v + q) - 3*u, u + v
u, v = fib_inner(n >> 1)
# L[m]
l = 2*v - u
if (n & 1):
q = (n & 2) - 1
return v * l + q
return u * l
Run Code Online (Sandbox Code Playgroud)
自从最初发布以来,我也略微改进了之前的结果.除了两个BigInt正方形之外,在F n和F n + 1上的分裂也有三个BigInt加法的开销和每个分裂的两个小的常数乘法.这种开销几乎可以完全通过分裂L n和L n + 1来消除:
以这种方式拆分需要像以前一样使用两个BigInt方块,但只需添加一个BigInt.但是,这些值需要与相应的斐波纳契数相关联.幸运的是,这可以通过单个除以5来实现:
因为已知商是整数,mpz_divexact_ui所以可以使用诸如GMP的精确除法方法.展开最外层的分割允许我们使用单个乘法计算最终值:
正如在Python中实现的那样,对于非常大的n(测试脚本)来说,这比前面的小n快(快5-10%)并且稍微快一些.
def fibonacci(n):
def fib_inner(n):
'''Returns L[n] and L[n+1]'''
if n == 0:
return mpz(2), mpz(1)
m = n >> 1
u, v = fib_inner(m)
q = (2, -2)[m & 1]
u = u * u - q
v = v * v + q
if (n & 1):
return v - u, v
return u, v - u
m = n >> 1
u, v = fib_inner(m)
# F[m]
f = (2*v - u) / 5
if (n & 1):
q = (n & 2) - 1
return v * f - q
return u * f
Run Code Online (Sandbox Code Playgroud)
1可以看出F n ~ O(n)的位数(或位)为:
使用Karatsuba乘法的运行时复杂度可以计算为:
2 Takahashi,D.(2000)," 用于计算大型斐波纳契数的快速算法 "(PDF),信息处理快报75,第243-246页.
Wil*_*ess 13
使用重复身份http://en.wikipedia.org/wiki/Fibonacci_number#Other_identities以步骤查找n-th数字log(n).你必须使用任意精度整数.或者,您可以通过在每个步骤使用模运算来计算模拟某些因子的精确答案.



注意到3n+3 == 3(n+1),我们可以设计一个单递归函数,在每一步计算两个连续的Fibonacci数,除以n3并根据余数值选择合适的公式.如果它在一步中@(3n+r,3n+r+1), r=0,1,2从一对@(n,n+1)中计算一对,所以没有双递归并且不需要记忆.
Haskell代码就在这里.
F(2n-1) = F(n-1)^2 + F(n)^2 === a' = a^2 + b^2
F(2n) = 2 F(n-1) F(n) + F(n)^2 === b' = 2ab + b^2
Run Code Online (Sandbox Code Playgroud)
似乎导致更快的代码.使用 "Lucas序列标识"可能是最快的(这是由于用户:primo,他引用了这个实现).
大多数人已经给你链接解释了Nth Fibonacci数的发现,顺便说一下,Power算法的工作方式与微小变化相同.
无论如何这是我的O(log N)解决方案.
package algFibonacci;
import java.math.BigInteger;
public class algFibonacci {
// author Orel Eraki
// Fibonacci algorithm
// O(log2 n)
public static BigInteger Fibonacci(int n) {
int num = Math.abs(n);
if (num == 0) {
return BigInteger.ZERO;
}
else if (num <= 2) {
return BigInteger.ONE;
}
BigInteger[][] number = { { BigInteger.ONE, BigInteger.ONE }, { BigInteger.ONE, BigInteger.ZERO } };
BigInteger[][] result = { { BigInteger.ONE, BigInteger.ONE }, { BigInteger.ONE, BigInteger.ZERO } };
while (num > 0) {
if (num%2 == 1) result = MultiplyMatrix(result, number);
number = MultiplyMatrix(number, number);
num/= 2;
}
return result[1][1].multiply(BigInteger.valueOf(((n < 0) ? -1:1)));
}
public static BigInteger[][] MultiplyMatrix(BigInteger[][] mat1, BigInteger[][] mat2) {
return new BigInteger[][] {
{
mat1[0][0].multiply(mat2[0][0]).add(mat1[0][1].multiply(mat2[1][0])),
mat1[0][0].multiply(mat2[0][1]).add(mat1[0][1].multiply(mat2[1][1]))
},
{
mat1[1][0].multiply(mat2[0][0]).add(mat1[1][1].multiply(mat2[1][0])),
mat1[1][0].multiply(mat2[0][1]).add(mat1[1][1].multiply(mat2[1][1]))
}
};
}
public static void main(String[] args) {
System.out.println(Fibonacci(8181));
}
}
Run Code Online (Sandbox Code Playgroud)