是否有任何算法可以在亚线性时间内计算第n个斐波那契数?
是否有任何算法可以在亚线性时间内计算第n个斐波那契数?
Answers:
所述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);
}
phi^n / sqrt(5) + 1/2
where的底数phi = (1 + sqrt(5)) / 2
。这是事实。其次,我理解其他人对答案长度的O(n)
看法,但是我在答案中加了一个注解,假设原始数学运算花费的时间是恒定的(除非绑定输入,否则我不会知道)。我的观点是,我们可以在O(log n)
算术运算中找到第n个斐波那契数。
遵循Pillsy对矩阵乘幂的引用,例如对于矩阵
M = [1 1] [1 0]
然后
fib(n)= M n 1,2
使用重复乘法将矩阵乘幂不是很有效。
矩阵求幂的两种方法是分而治之,即以O(ln n)步长产生M n,或者是固定时间的特征值分解,但由于有限的浮点精度而可能引入误差。
如果您想要的精确值大于浮点实现的精度,则必须基于以下关系使用O(ln n)方法:
M n =(M n / 2)2如果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 1和X 2:
如果X 1 = [ X 1,1,X 1,2 ] 中号。X 1 1 =λ 1 X 1 X 1,1 + X 1,2 =λ 1 X 1,1 X 1,1 =λ 1 X 1,2 => X 1 = [Φ,1] X 2 = [1-Φ,1]
这些向量给U:
U = [ X 1,1,X 2,2 ] [ X 1,1,X 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
这与其他地方给出的公式一致。
您可以从递归关系中得出它,但是在工程计算和仿真中,计算大型矩阵的特征值和特征向量是一项重要的活动,因为它可以提供方程组系统的稳定性和谐波,并允许将矩阵有效地提高到高幂。
如果您想要确切的数字(是“ 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
是O(n)
由于请求的结果为O(n),因此无法在少于O(n)的时间内进行计算。
如果只希望答案的低位数字,则可以使用矩阵求幂方法在亚线性时间内进行计算。
O(n*log n)
基于比较的n
数字序列排序,其中每个数字都有O(log n)
数字?
if even(count)
是正确的。序列从零开始(零斐波那契数为零):0,1,1,2,3,5,8,13,...
您也可以通过对整数矩阵求幂来实现。如果你有矩阵
/ 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
。
维基百科有封闭式解决方案 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;
}
|1 - phi|^n / sqrt(5) < 1/2
whenn
是非负整数这一事实,可以避免计算两个指数的需要。
对于真正的大对象,此递归函数有效。它使用以下方程式:
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个斐波那契数等。
这是我的递归版本,它递归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)]
简化右侧会导致偶数情况。
定点运算不准确。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
。
除了通过数学方法进行微调之外,最佳的最佳解决方案之一(我认为)是使用字典以避免重复计算。
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秒
请参阅此处的分治算法
该链接具有此问题的其他一些答案中提到的矩阵幂的伪代码。
您可以使用怪异的平方根方程来获得确切的答案。原因是$ \ 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
这是在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
。
我遇到了一些有效的时间复杂度计算斐波那契的方法,以下是其中一些-
方法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
我们首先应该注意到,斐波那契数(F(n))
随着增长很快,n
并且对于大于93的数,它不能用64位表示。n
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
和可能加1 x
,我们也可以计算的新的值F(x)
使用的较早的值F(x)
和F(x+1)
与上述方程。
由于迭代次数将为对数n
,因此总的(大数)运算也将为对数n
。
有关更多详细信息,请参阅本文的“改进算法”部分。