it-swarm.com.de

nte Fibonacci-Zahl in sublinearer Zeit

Gibt es einen Algorithmus, um die n-te Fibonacci-Zahl in sublinearer Zeit zu berechnen?

73
Biswajyoti Das

Die nth Fibonacci-Nummer wird durch gegeben

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

woher 

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

Angenommen, die primitiven mathematischen Operationen (+, -, * und /) sind O(1), können Sie dieses Ergebnis verwenden, um die nth Fibonacci-Zahl in O(log n) time (O(log n)) zu berechnen, da die Exponentiation in der Formel verwendet wird.

In 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);
}
63
jason

Aus Pillsys Hinweis auf die Matrix-Exponentiation folgt, wie bei der Matrix

M  = [1 1] 
 [1 0] 

dann 

flunkerei(n) = Mn1,2

Es ist nicht sehr effizient, Matrizen durch wiederholte Multiplikation in Potenzen zu erhöhen.

Zwei Ansätze für die Matrixexponentiation sind Dividieren und Erobern, woraus sich ergibt Mn im O(ln n) Schritte oder Eigenwertzerlegung, die eine konstante Zeit ist, kann jedoch aufgrund begrenzter Gleitkomma-Genauigkeit Fehler verursachen.

Wenn Sie einen exakten Wert wünschen, der die Genauigkeit Ihrer Gleitkommaimplementierung übersteigt, müssen Sie den auf dieser Beziehung basierenden Ansatz O (Ln n) verwenden: 

Mn  = (Mn/ 2)2  ob n  sogar
 = M·Mn-1  ob n  ist ungerade

Die Eigenwertzerlegung auf M findet zwei Matrizen U und Λ so dass Λ ist diagonal und 

M   = UΛU-1Mn  = ( UΛU-1nUΛU-1UΛU-1UΛU-1  ... n mal 
 = UΛΛΛ  ... U-1  
 = UΛnU-1
Die diagonale Matrix anheben Λ zum ndie Macht ist eine einfache Sache, indem jedes Element in angehoben wird Λ zum nth, so ergibt sich eine O(1) - Methode zum Anheben M zum ndie Macht. Die Werte in Λ sind wahrscheinlich keine ganzen Zahlen, daher wird ein Fehler auftreten.

Definieren Λ für unsere 2x2 Matrix als

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

Um jeden zu finden λ, wir lösen 

  |M  - λIch| = 0

was gibt

  |M  - λIch| = -λ (1 - λ) - 1 

 λ² - λ - 1 = 0 

unter Verwendung der quadratischen Formel 

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

Wenn Sie Jasons Antwort gelesen haben, können Sie sehen, wohin das gehen wird.

Lösung für die Eigenvektoren X1 und X2:

ob X1  = [ X1,1X1,2  ] 

 M.X1 1  = λ1X1X1,1  + X1,2  = λ1X1,1X1,1       = λ1X1,2

 => 
 X1  = [Φ, 1] 
 X2  = [1 - Φ, 1] 

Diese Vektoren ergeben U:

U  = [ X1,1X2,2  ] 
 [ X1,1X2,2  ] 

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

Umkehren U mit

EIN    = [a b] 
 [c d] 
 => 
EIN-1  = (1/|EIN| ) [d -b] 
 [-c a] 

so U-1 ist gegeben durch

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

Gesundheitsüberprüfung:

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

 Es sei Ψ = 1 - Φ, der andere Eigenwert 
.__, da Φ eine Wurzel von λ²-λ-1 = 0 ist 
 so -ΨΦ = Φ²-Φ = 1 
 und Ψ + Φ = 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

Die Überprüfung der Vernunft ist also gültig.

Jetzt haben wir alles, was wir berechnen müssen Mn1,2:

Mn  = UΛnU-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  ] wie ΨΦ = -1 

 = (√5)-1  [Φn+1n+1       Φnn  ] 
 [Φnn       Φn-1n-1  ] 

so

flunkerei(n) = Mn1,2
 = (Φn  - (1-Φ)n  )/√5 

Die stimmt mit der anderswo angegebenen Formel überein.

Sie können es aus einer Wiederholungsbeziehung ableiten, aber in der Engineeringberechnung und Simulation ist die Berechnung der Eigenwerte und Eigenvektoren von großen Matrizen eine wichtige Aktivität, da sie Stabilität und Oberwellen von Gleichungssystemen liefert und Matrizen effizient auf hohe Potenzen anhebt.

96
Pete Kirkham

Wenn Sie die exakte Zahl wünschen (was ein "Bignum" ist und nicht ein Int/Float), dann habe ich Angst, dass

Es ist unmöglich!

Wie oben angegeben, lautet die Formel für Fibonacci-Zahlen:

fib n = Boden (phin/ √5 + 1/2)

fib n ~ = phin/ √5

Wie viele Ziffern ist fib n?

numDigits (fib n) = log (fib n) = log (phin/ √5) = log phin log √5 = n * log phi-log √5

numDigits (fib n) = n * const + const

es ist O (n)

Da das angeforderte Ergebnis O (n) ist, kann es nicht in weniger als O (n) Zeit berechnet werden.

Wenn Sie nur die unteren Ziffern der Antwort wünschen, können Sie mit der Matrix-Exponentiation-Methode in sublinearer Zeit berechnen.

54
yairchu

Eine der Übungen in SICP befasst sich damit, welche Antwort hier hat.

Im imperativen Stil würde das Programm so aussehen

FunktionFlunkerei(anzahl) 
 ein  ← 1 
 b  ← 0 
 p  ← 0 
 q  ← 1 

 Währendanzahl  > 0 TunOb  Sogar(anzahlDannp  ← p² + q² 
 q  ← 2pq  + q² 
 anzahl  ← anzahl  ÷ 2 
 Elseein  ← bq  + aq  + apb  ← bp  + aqanzahl  ← anzahl  - 1 
 Ende wennEnde währendRückkehrbFunktion beenden
33
Nietzche-jou

Sie können dies auch tun, indem Sie eine Matrix aus ganzen Zahlen potenzieren. Wenn Sie die Matrix haben 

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

dann ist (M^n)[1, 2] gleich der nth Fibonacci-Nummer, wenn [] ein Matrix-Index ist und ^ eine Matrix-Exponentiation ist. Bei einer Matrix mit fester Größe kann die Potenzierung auf eine positive integrale Potenz in O (log n) -Zeit wie bei reellen Zahlen erfolgen. 

EDIT: Natürlich können Sie abhängig von der gewünschten Antwortart mit einem Algorithmus mit konstanter Zeit davonkommen. Wie die anderen Formeln zeigen, wächst die nth Fibonacci-Zahl exponentiell mit n. Selbst mit 64-Bit-Ganzzahlen ohne Vorzeichen benötigen Sie nur eine Nachschlagetabelle mit 94 Einträgen, um den gesamten Bereich abzudecken.

SECOND EDIT: Wenn Sie die Matrix zunächst exponentiell mit einer Eigendekomposition ausführen, entspricht dies genau der unten stehenden Lösung von JDunkerly. Die Eigenwerte dieser Matrix sind (1 + sqrt(5))/2 und (1 - sqrt(5))/2.

24
Pillsy

Wikipedia hat eine geschlossene Formularlösung http://en.wikipedia.org/wiki/Fibonacci_number

Oder in 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ür wirklich große funktioniert diese rekursive Funktion. Es verwendet die folgenden Gleichungen:

F(2n-1) = F(n-1)^2 + F(n)^2
F(2n) = (2*F(n-1) + F(n)) * F(n)

Sie benötigen eine Bibliothek, mit der Sie mit großen Ganzzahlen arbeiten können. Ich benutze die BigInteger-Bibliothek von https://mattmccutchen.net/bigint/

Beginnen Sie mit einer Reihe von Fibonacci-Zahlen. Verwenden Sie fibs [0] = 0, fibs [1] = 1, fibs [2] = 1, fibs [3] = 2, fibs [4] = 3 usw. In diesem Beispiel verwende ich ein Array der ersten 501 (zählt 0). Die ersten 500 Nicht-Null-Fibonacci-Zahlen finden Sie hier: http://home.hiwaay.net/~jalison/Fib500.html . Es erfordert etwas Bearbeitung, um es in das richtige Format zu bringen, aber das ist nicht zu schwer.

Dann können Sie mit dieser Funktion (in C) eine beliebige Fibonacci-Nummer finden:

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

Ich habe dies für die 25.000ste Fibonacci-Nummer und dergleichen getestet.

3
user3137939

Hier ist meine rekursive Version, die log (n) mal rekursiert. Ich denke, es ist am einfachsten in der rekursiven Form zu lesen:

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)

Es funktioniert, weil Sie fib(n),fib(n-1) mit fib(n-1),fib(n-2) berechnen können, wenn n ungerade ist und wenn n gerade ist, können Sie fib(n),fib(n-1) mit fib(n/2),fib(n/2-1) berechnen.

Der Basisfall und der ungerade Fall sind einfach. Um den geraden Fall abzuleiten, beginnen Sie mit a, b, c als aufeinander folgende Fibonacci-Werte (z. B. 8,5,3) und schreiben Sie sie in eine Matrix mit a = b + c. Beachten:

[1 1] * [a b]  =  [a+b a]
[1 0]   [b c]     [a   b]

Daraus ergibt sich, dass eine Matrix der ersten drei Fibonacci-Zahlen, mal eine Matrix aus drei aufeinander folgenden Fibonacci-Zahlen, der nächsten entspricht. Also wissen wir das:

      n
[1 1]   =  [fib(n+1) fib(n)  ]
[1 0]      [fib(n)   fib(n-1)]

So:

      2n                        2
[1 1]    =  [fib(n+1) fib(n)  ]
[1 0]       [fib(n)   fib(n-1)]

Die Vereinfachung der rechten Seite führt zum geraden Fall.

3
Eyal

mit 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

Abgesehen von der Feinabstimmung durch mathematische Ansätze ist die Verwendung eines Wörterbuchs eine der besten Optimallösungen (ich glaube daran), um sich wiederholende Berechnungen zu vermeiden.

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

Wir beginnen mit einem trivialen Wörterbuch (die ersten beiden Werte der Fibonacci-Sequenz) und fügen dem Wörterbuch ständig Fibonacci-Werte hinzu.

Es dauerte etwa 0,7 Sekunden für die ersten 100000 Fibonacci-Werte (Intel Xeon CPU E5-2680 bei 2,70 GHz, 16 GB RAM, Windows 10-64-Bit-Betriebssystem)

1
bilalsamioguz

Festpunktarithmetik ist ungenau. Jasons C # -Code gibt eine falsche Antwort für n = 71 (308061521170130 statt 308061521170129) und darüber hinaus.

Verwenden Sie für eine korrekte Antwort ein Computeralgebra-System. Sympy ist eine solche Bibliothek für Python. Es gibt eine interaktive Konsole unter http://live.sympy.org/ . Kopieren Sie diese Funktion und fügen Sie sie ein

phi = (1 + sqrt(5)) / 2
def f(n):
    return floor(phi**n / sqrt(5) + 1/2)

Dann berechnen Sie

>>> f(10)
55

>>> f(71)
308061521170129

Vielleicht möchten Sie phi überprüfen. 

0
Colonel Panic

siehe Algorithmus teilen und erobern hier

Der Link hat einen Pseudocode für die Matrixexponentiation, die in einigen anderen Antworten zu dieser Frage erwähnt wird.

0
Chinmay Lokesh

Hier ist ein Einzeiler, der F (n) unter Verwendung von Ganzzahlen der Größe O (n) in arithmetischen O (log n) -Operationen berechnet:

for i in range(1, 50):
    print(i, pow(2<<i, i, (4<<2*i)-(2<<i)-1)//(2<<i))

Die Verwendung von Ganzzahlen der Größe O(n) ist sinnvoll, da dies mit der Größe der Antwort vergleichbar ist.

Um dies zu verstehen, sei phi der goldene Schnitt (die größte Lösung für x ^ 2 = x + 1) und F(n) die n-te Fibonacci-Zahl, wobei F (0) = 0, F (1) = F (2) = 1

Nun ist phi ^ n = F(n-1) + F (n) phi.

Beweis durch Induktion: phi ^ 1 = 0 + 1 * phi = F(0) + F (1) phi. Und wenn phi ^ n = F(n-1) + F (n) phi, dann 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. Der einzige schwierige Schritt in dieser Berechnung ist der, der phi ^ 2 durch (1 + phi) ersetzt, was folgt, weil phi der goldene Schnitt ist.

Auch Zahlen der Form (a + b * phi), bei denen a, b ganze Zahlen sind, werden unter Multiplikation geschlossen.

Beweis: (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.

Mit dieser Darstellung kann man phi ^ n in O (log n) -Integer-Operationen unter Verwendung der Potenzierung durch Quadrieren berechnen. Das Ergebnis ist F (n-1) + F (n) phi, woraus man die n-te Fibonacci-Zahl ablesen kann.

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

Beachten Sie, dass der Großteil dieses Codes eine standardmäßige Exponentiation-by-Squaring-Funktion ist.

Um zu dem Einzeiler zu gelangen, der diese Antwort startet, kann man feststellen, dass man für die Darstellung von Phi durch eine ausreichend große Ganzzahl X(a+b*phi)(c+d*phi) Als Ganzzahloperation (a+bX)(c+dX) modulo (X^2-X-1) Ausführen kann. Dann kann die Funktion pow durch die Standardfunktion Python pow ersetzt werden (die bequemerweise ein drittes Argument z enthält, das das Modulo-Ergebnis berechnet z. Als X wird 2<<i gewählt.

0
Paul Hankin

Sie können die seltsame Quadratwurzel-Gleichung verwenden, um eine genaue Antwort zu erhalten. Der Grund ist, dass der $\sqrt (5) $ am Ende herausfällt. Sie müssen nur die Koeffizienten mit Ihrem eigenen Multiplikationsformat verfolgen.

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