サブ線形時間でn番目のフィボナッチ数を計算するアルゴリズムはありますか?
n
thフィボナッチ数は次で与えられます
_f(n) = Floor(phi^n / sqrt(5) + 1/2)
_
どこ
_phi = (1 + sqrt(5)) / 2
_
プリミティブな数学演算(_+
_、_-
_、_*
_、および_/
_)がO(1)
であると仮定すると、この結果を使用してn
th O(log n)
timeのフィボナッチ数(式の累乗のため、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);
}
_
Pillsyの行列累乗法への参照に続きます。
M = [1 1] [1 0]
それから
フィブ(n)= Mn1,2
繰り返し乗算を使用して行列を累乗することは、あまり効率的ではありません。
行列の累乗法の2つのアプローチは、除算と征服です。 Mn に O(ln n)ステップ、または一定時間であるが、浮動小数点の精度が制限されているためにエラーが発生する可能性がある固有値分解。
浮動小数点実装の精度より大きい正確な値が必要な場合は、この関係に基づいてO(ln n)アプローチを使用する必要があります。
Mn =(Mn/ 2)2 もし n 偶数 = M・Mn-1 もし n 奇数です
上の固有値分解 M 2つの行列を見つける うん そして Λ そのような Λ 対角線であり、
M = うんΛうん-1Mn =( うんΛうん-1) n = うんΛうん-1うんΛうん-1うんΛうん-1 ... n回 = うんΛΛΛ ... うん-1 = うんΛnうん-1ΛnΛnMnΛ
定義する Λ 2x2行列として
Λ = [λ1 0] = [0λ2 ]
それぞれを見つけるには λ、我々は解決する
|M -λ私| = 0
与える
|M -λ私| =-λ(1-λ)-1 λ²-λ-1 = 0
二次式を使用して
λ=(-b±√(b²-4ac))/ 2a =(1±√5)/ 2 {λ1、λ2 } = {Φ、1-Φ}ここで、Φ=(1 +√5)/ 2
Jasonの答えを読んだら、これがどこに行くのかを見ることができます。
固有ベクトルの解 バツ1 そして バツ2:
if バツ1 = [ バツ1,1、 バツ1,2 ] M。バツ1 1 =λ1バツ1バツ1,1 + バツ1,2 =λ1バツ1,1バツ1,1 =λ1バツ1,2 => バツ1 = [Φ、1] バツ2 = [1-Φ、1]
これらのベクトルは うん:
うん = [ バツ1,1、 バツ2,2 ] [ バツ1,1、 バツ2,2 ] = [Φ、1-Φ] [1、1]
反転 うん を使用して
A = [a b] [c d] => A-1 =(1/|A| )[d -b] [-c a]
そう うん-1 によって与えられます
うん-1 =(1 /(Φ-(1-Φ))[1Φ-1] [-1Φ] うん-1 =(√5)-1 [1Φ-1] [-1Φ]
サニティーチェック:
うん-1 =(√5)-1 [Φ1-Φ]。 [Φ0]。 [1Φ-1] [1 1] [01-Φ] [-1Φ] letΨ=1-Φ、他の固有値 Φはλ²-λ-1= 0の根である so-ΨΦ=Φ²-Φ= 1 およびΨ+Φ= 1 うん-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
したがって、健全性チェックが保持されます。
計算に必要なものはすべて揃った Mn1,2:
Mn = うんΛnうん-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] [-Ψn -Ψn-1 ]としてΨΦ= -1 =(√5)-1 [Φn+1-Ψn+1 Φn-Ψn ] [Φn-Ψn Φn-1-Ψn-1 ]
そう
フィブ(n)= Mn1,2 =(Φn -(1-Φ)n )/√5
他の場所で与えられた式と一致します。
回帰関係から導出することができますが、エンジニアリングコンピューティングとシミュレーションでは、大きな行列の固有値と固有ベクトルを計算することは、方程式系の安定性と高調波を与え、行列を効率的に高出力に上げることができるため、重要なアクティビティです。
正確な数値(int/floatではなく「bignum」)が必要な場合、私はそれを恐れています
不可能です!
上記のように、フィボナッチ数の公式は次のとおりです。
fib n =床(phin/√5+ 1/2)
fib n〜= phin/√5
fib n
は何桁ですか?
numDigits(fib n)= log(fib n)= log(phin/√5)=ファイファイn -log√5= n * log phi-log√5
numDigits(fib n)= n * const + const
それは[〜#〜] o [〜#〜](n)
要求された結果は[〜#〜] o [〜#〜](n)、[〜#〜] o [〜#〜](n)時間。
回答の下位桁のみが必要な場合は、行列累乗法を使用して準線形時間で計算できます。
整数の行列を累乗することでもできます。マトリックスがある場合
/ 1 1 \
M = | |
\ 1 0 /
(M^n)[1, 2]
が行列の添字で[]
が行列の累乗の場合、^
はn
thフィボナッチ数に等しくなります。固定サイズの行列の場合、正の整数のべき乗のべき乗は、実数の場合と同じ方法でO(log n)時間で実行できます。
編集:もちろん、あなたが望む答えのタイプによっては、一定時間のアルゴリズムで逃げることができるかもしれません。他の式が示すように、n
thフィボナッチ数はn
で指数関数的に増加します。 64ビットの符号なし整数であっても、範囲全体をカバーするために必要なのは94エントリのルックアップテーブルのみです。
SECOND EDIT:固有分解で行列指数関数を最初に実行することは、以下の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;
}
本当に大きなものの場合、この再帰関数は機能します。次の方程式を使用します。
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)
を計算できるためです。
基本ケースと奇数ケースは単純です。偶数の場合を導出するには、連続したフィボナッチ値(例:8,5,3)としてa、b、cから始め、a = b + cで行列に書き込みます。通知:
_[1 1] * [a b] = [a+b a]
[1 0] [b c] [a b]
_
このことから、最初の3つのフィボナッチ数の行列に3つの連続したフィボナッチ数の行列を掛けたものが、次の行列に等しいことがわかります。だから私たちはそれを知っています:
_ 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)]
_
右側を単純化すると、偶然になります。
[〜#〜] 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つは(反復する計算を避けるために辞書を使用することです)。
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))
簡単な辞書(フィボナッチ数列の最初の2つの値)から始め、フィボナッチ値を辞書に絶えず追加します。
最初の100000フィボナッチ値(Intel Xeon CPU E5-2680 @ 2.70 GHz、16 GB RAM、Windows 10-64ビットOS)には約0.7秒かかりました
固定小数点演算は不正確です。 JasonのC#コードは、n = 71(308061521170129の代わりに308061521170130)以降で不正な答えを返します。
正解を得るには、計算代数システムを使用してください。 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
を調べてみてください。
分割統治アルゴリズムを参照してください こちら
リンクには、この質問の他の回答のいくつかで言及されている行列累乗の擬似コードがあります。
これは、O(log n)算術演算でサイズO(n)の整数を使用してF(n)を計算する1ライナーです。
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。この計算で唯一の注意が必要なステップは、phi ^ 2を(1 + phi)に置き換えるステップです。
また、a、bが整数である形式(a + b * phi)の数値は、乗算で閉じられます。
証明:(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。
この表現を使用して、2乗によるべき乗を使用してO(log n)整数演算でphi ^ 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])
このコードの大部分は、標準の2乗累乗関数であることに注意してください。
この答えを開始するワンライナーに到達するために、十分な大きさの整数X
でphiを表すことに注意できます。整数演算(a+b*phi)(c+d*phi)
として(a+bX)(c+dX) modulo (X^2-X-1)
を実行できます。 pow
関数は、標準のPython pow
関数(結果のモジュロを計算する3番目の引数z
を含むのが便利です)で置き換えることができますz
。選択されたX
は2<<i
です。
奇妙な平方根方程式を使用して正確な答えを得ることができます。その理由は、最後に$\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 __== "__main__":
assert rootipower(1,2,3,3) == (37,30) # 1+2sqrt(3) **3 => 13 + 4sqrt(3) => 39 + 30sqrt(3)
assert fib(10)==55