web-dev-qa-db-ja.com

準線形時間のn番目のフィボナッチ数

サブ線形時間でn番目のフィボナッチ数を計算するアルゴリズムはありますか?

74
Biswajyoti Das

nthフィボナッチ数は次で与えられます

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

どこ

_phi = (1 + sqrt(5)) / 2
_

プリミティブな数学演算(_+_、_-_、_*_、および_/_)がO(1)であると仮定すると、この結果を使用してnth 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);
}
_
64
jason

Pillsyの行列累乗法への参照に続きます。

M = [1 1] 
 [1 0] 

それから

フィブn)= Mn1,2

繰り返し乗算を使用して行列を累乗することは、あまり効率的ではありません。

行列の累乗法の2つのアプローチは、除算と征服です。 Mn に Oln n)ステップ、または一定時間であるが、浮動小数点の精度が制限されているためにエラーが発生する可能性がある固有値分解。

浮動小数点実装の精度より大きい正確な値が必要な場合は、この関係に基づいてO(ln n)アプローチを使用する必要があります。

Mn =(Mn/ 22 もし n 偶数
 = MMn-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+1n+1      Φnn ] 
 [Φnn      Φn-1n-1 ] 

そう

フィブn)= Mn1,2
 =(Φn -(1-Φ)n )/√5

他の場所で与えられた式と一致します。

回帰関係から導出することができますが、エンジニアリングコンピューティングとシミュレーションでは、大きな行列の固有値と固有ベクトルを計算することは、方程式系の安定性と高調波を与え、行列を効率的に高出力に上げることができるため、重要なアクティビティです。

98
Pete Kirkham

正確な数値(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)時間。

回答の下位桁のみが必要な場合は、行列累乗法を使用して準線形時間で計算できます。

54
yairchu

SICPの運動 の1つはこれについてであり、これには here。

命令型では、プログラムは次のようになります

関数フィブカウント)
 a ←1 
 b ←0 
 p ←0 
 q ←1 
 
 ながらカウント > 0 行うもし 偶数(カウント) それからp ← p²+ q²
 q ←2pq + q²
 カウント ← カウント ÷2 
 Elsea ← bq + 水溶液 + aPb ← bp + 水溶液カウント ← カウント -1 
 終了する終わりながら戻るb終了機能
33
Nietzche-jou

整数の行列を累乗することでもできます。マトリックスがある場合

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

(M^n)[1, 2]が行列の添字で[]が行列の累乗の場合、^nthフィボナッチ数に等しくなります。固定サイズの行列の場合、正の整数のべき乗のべき乗は、実数の場合と同じ方法でO(log n)時間で実行できます。

編集:もちろん、あなたが望む答えのタイプによっては、一定時間のアルゴリズムで逃げることができるかもしれません。他の式が示すように、nthフィボナッチ数はnで指数関数的に増加します。 64ビットの符号なし整数であっても、範囲全体をカバーするために必要なのは94エントリのルックアップテーブルのみです。

SECOND EDIT:固有分解で行列指数関数を最初に実行することは、以下のJDunkerlyのソリューションとまったく同じです。この行列の固有値は、(1 + sqrt(5))/2(1 - sqrt(5))/2です。

24
Pillsy

ウィキペディアには閉じた形式のソリューションがあります 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;
    }
5
JDunkerley

本当に大きなものの場合、この再帰関数は機能します。次の方程式を使用します。

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番目のフィボナッチ数などでテストしました。

3
user3137939

これが、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)]
_

右側を単純化すると、偶然になります。

3
Eyal

[〜#〜] 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
George Dontas

数学的なアプローチによる微調整とは別に、最善の解決策の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秒かかりました

1
bilalsamioguz

固定小数点演算は不正確です。 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を調べてみてください。

0
Colonel Panic

分割統治アルゴリズムを参照してください こちら

リンクには、この質問の他の回答のいくつかで言及されている行列累乗の擬似コードがあります。

0
Chinmay Lokesh

これは、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。選択されたX2<<iです。

0
Paul Hankin

奇妙な平方根方程式を使用して正確な答えを得ることができます。その理由は、最後に$\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
0
Scott