it-swarm.com.de

Warum ist die Fibonacci-Sequenz groß? O(2^n) anstelle von O (logn)?

Ich habe vor einiger Zeit diskrete Mathematik (in der ich etwas über den Hauptsatz Big Theta/Omega/O gelernt habe) genommen und den Unterschied zwischen O(logn) und O (2 ^ n) ( nicht im theoretischen Sinne von Big Oh). Ich verstehe im Allgemeinen, dass Algorithmen wie Zusammenführen und schnelles Sortieren O(nlogn) sind, da sie das anfängliche Eingabearray wiederholt in Unterarrays aufteilen, bis jedes Unterarray die Größe 1 hat, bevor der Baum rekursiv gesichert und eine Rekursion erstellt wird Baum, der die Höhe logn + 1 hat. Wenn Sie jedoch die Höhe eines rekursiven Baums mit n/b ^ x = 1 berechnen (wenn die Größe des Teilproblems 1 geworden ist, wie in einer Antwort angegeben hier ), dann Es scheint, dass Sie immer feststellen, dass die Höhe des Baumes log (n) ist.

Wenn Sie die Fibonacci-Sequenz mit einer Rekursion lösen, erhalten Sie vermutlich auch einen Baum der Größe logn, aber aus irgendeinem Grund ist das Big O des Algorithmus O (2 ^ n). Ich dachte, dass der Unterschied vielleicht darin besteht, dass Sie sich alle Fib-Nummern für jedes Unterproblem merken müssen, um die tatsächliche Fib-Nummer zu erhalten, was bedeutet, dass der Wert an jedem Knoten abgerufen werden muss, aber es scheint, dass bei der Zusammenführungssortierung der Wert jedes Knotens muss ebenfalls verwendet (oder zumindest sortiert) werden. Dies unterscheidet sich jedoch von der binären Suche, bei der Sie nur bestimmte Knoten besuchen, die auf Vergleichen basieren, die auf jeder Ebene des Baums durchgeführt wurden. Ich denke, hier liegt die Verwirrung.

Was genau bewirkt also, dass die Fibonacci-Sequenz eine andere zeitliche Komplexität aufweist als Algorithmen wie Merge/Quick Sort?

6
loremIpsum1771

Die anderen Antworten sind richtig, machen es aber nicht klar - woher kommt der große Unterschied zwischen dem Fibonacci-Algorithmus und den Divide-and-Conquer-Algorithmen? Tatsächlich ist die Form des Rekursionsbaums für beide Funktionsklassen gleich - es ist ein binärer Baum.

Der Trick zu verstehen ist eigentlich sehr einfach: Betrachten Sie das size des Rekursionsbaums als Funktion der Eingabegröße n.

Erinnern Sie sich zuerst an einige grundlegende Fakten zu binären Bäumen :

  • Die Anzahl der Blätter n ist ein binärer Baum und entspricht der Anzahl der Nicht-Blatt-Knoten plus eins. Die Größe eines binären Baums beträgt daher 2n-1.
  • In einem binären Perfekt-Baum haben alle Nicht-Blattknoten zwei untergeordnete Elemente.
  • Die Höhe h für einen perfekten binären Baum mit n Blättern ist gleich log(n), für einen zufälligen binären Baum: h = O(log(n)) und für einen entarteten binären Baum h = n-1.

Intuitiv:

  • Um ein Array von n-Elementen mit einem rekursiven Algorithmus zu sortieren, enthält der Rekursionsbaum n leaves . Daraus folgt, dass Breite des Baumsnist, die Höhe des Baums im DurchschnittO(log(n))ist undO(n)im schlimmsten Fall.

  • Um ein Fibonacci-Sequenzelement k mit dem rekursiven Algorithmus zu berechnen, hat der Rekursionsbaum k levels (um zu sehen, warum fib(k)fib(k-1) aufruft, was fib(k-2) aufruft, usw.). Daraus folgt, dass height des Baumskist. Um eine Untergrenze für die Breite und Anzahl der Knoten im Rekursionsbaum zu schätzen, müssen Sie Folgendes berücksichtigen: Da fib(k) auch fib(k-2) aufruft, gibt es als Teil des Rekursionsbaums einen perfekten Binärbaum der Höhe k/2. Wenn extrahiert, hätte dieser perfekte Teilbaum 2k/2 Blattknoten. Das width des Rekursionsbaums ist also mindestens O(2^{k/2}) oder entsprechend2^O(k).

Der entscheidende Unterschied ist:

  • bei Divide-and-Conquer-Algorithmen ist die Eingabegröße die width des Binärbaums.
  • für den Fibonnaci-Algorithmus ist die Eingabegröße die height des Baums.

Daher ist die Anzahl der Knoten in der Baumstruktur im ersten Fall O(n), im zweiten Fall 2^O(n). Der Fibonacci-Baum ist viel größer als die Eingabegröße.

Sie erwähnen Master-Theorem ; Der Satz kann jedoch nicht zur Analyse der Komplexität von Fibonacci angewendet werden, da er nur für Algorithmen gilt, bei denen die Eingabe tatsächlich geteilt auf jeder Rekursionsebene ist. Fibonacci teilt die Eingabe mit nicht; Tatsächlich erzeugen die Funktionen auf Ebene i fast doppelt so viel Eingabe für die nächste Ebene i+1.

13
kfx

Um den Kern der Frage zu beantworten, nämlich "Warum Fibonacci und nicht Mergesort", sollten Sie sich auf diesen entscheidenden Unterschied konzentrieren:

  • Der Baum, den Sie von Mergesort erhalten, enthält N Elemente für jede Ebene, und es gibt Protokollniveaus (n).
  • Der Baum, den Sie von Fibonacci erhalten, hat N Ebenen, da in der Formel für F (N) F(n-1) vorhanden ist, und die Anzahl der Elemente für jede Ebene kann sehr unterschiedlich sein: Dies kann der Fall sein sehr niedrig (in der Nähe der Wurzel oder in der Nähe der untersten Blätter) oder sehr hoch. Dies liegt natürlich an der wiederholten Berechnung derselben Werte.

Um zu sehen, was ich unter "wiederholte Berechnung" verstehe, schauen Sie sich den Baum für die Berechnung von F (6) an:

 fib(6) computation tree
Fibonacci-Baumbild von: http://composingprograms.com/pages/28-efficiency.html

Wie oft sehen Sie, dass F(3) berechnet wird?

4
wil93

Betrachten Sie die folgende Implementierung

int fib(int n)
{
    if(n < 2)
      return n;
    return fib(n-1) + fib(n-2)
}

Wir bezeichnen T(n) die Anzahl der Operationen, die fib zur Berechnung von fib(n) durchführt. Da fib(n)fib(n-1) und fib(n-2) aufruft, bedeutet dies, dass T(n) mindestens T(n-1) + T(n-2) ist. Dies bedeutet wiederum, dass T(n) > fib(n). Es gibt eine direkte Formel von fib(n), die eine gewisse Konstante für n ist. Daher ist T(n) zumindest exponentiell. QED.

3
Armen Tsirunyan

Mit dem rekursiven Algorithmus haben Sie ungefähr 2 ^ N Operationen (Ergänzungen) für Fibonacci (N). Dann ist es O (2 ^ N).

Mit einem Cache (Memoization) haben Sie ungefähr N Operationen, dann ist es ist O (N).

Algorithmen mit Komplexität O (N log N) sind oft eine Konjunktion, bei der jedes Element (O (N)) iteriert, die Rekursion aufgeteilt und zusammengeführt wird. Split by 2 => Sie protokollieren N Rekursionen. 

Meines Erachtens besteht der Fehler in Ihrer Argumentation darin, dass bei Verwendung einer rekursiven Implementierung zur Auswertung von f(n), wobei f die Fibonacci-Sequenz bezeichnet, die Eingangsgröße um einen Faktor 2 (oder einen anderen Faktor) reduziert wird, was nicht der Fall ist. Jeder Aufruf (mit Ausnahme der 'Basisfälle' 0 und 1) verwendet genau zwei rekursive Aufrufe, da zuvor berechnete Werte nicht erneut verwendet werden können. In Anbetracht der Darstellung des Master-Theorems auf Wikipedia die Wiederholung

f(n) = f (n-1) + f(n-2)

ist ein Fall, für den der Master-Theorem nicht angewendet werden kann.

2
Codor

Die Komplexität der Zusammensortierzeit ist O (n log (n)). Der beste Fall für die schnelle Sortierung ist O (n log (n)), im schlechtesten Fall O (n ^ 2).

Die anderen Antworten erklären, warum naive rekursive Fibonacci O (2 ^ n) ist.

Wenn Sie lesen, dass Fibonacci (n) O (log (n)) sein kann, ist dies möglich, wenn die Berechnung mithilfe von Iteration und wiederholtem Quadrieren entweder mit der Matrixmethode oder der Lucas-Sequenzmethode berechnet wird. Beispielcode für die Lucas-Sequenzmethode (beachten Sie, dass n in jeder Schleife durch 2 geteilt wird):

/* lucas sequence method */
int fib(int n) {
    int a, b, p, q, qq, aq;
    a = q = 1;
    b = p = 0;
    while(1) {
        if(n & 1) {
            aq = a*q;
            a = b*q + aq + a*p;
            b = b*p + aq;
        }
        n /= 2;
        if(n == 0)
            break;
        qq = q*q;
        q = 2*p*q + qq;
        p = p*p + qq;
    }
    return b;
}
2
rcgldr