Bestimmen der Komplexität für rekursive Funktionen (Big O-Notation)


266

Ich habe morgen eine Informatik-Halbzeit und brauche Hilfe bei der Bestimmung der Komplexität dieser rekursiven Funktionen. Ich weiß, wie man einfache Fälle löst, aber ich versuche immer noch zu lernen, wie man diese schwierigeren Fälle löst. Dies waren nur einige der Beispielprobleme, die ich nicht herausfinden konnte. Jede Hilfe wäre sehr dankbar und würde mir beim Studium sehr helfen. Danke!

int recursiveFun1(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun1(n-1);
}

int recursiveFun2(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun2(n-5);
}

int recursiveFun3(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun3(n/5);
}

void recursiveFun4(int n, int m, int o)
{
    if (n <= 0)
    {
        printf("%d, %d\n",m, o);
    }
    else
    {
        recursiveFun4(n-1, m+1, o);
        recursiveFun4(n-1, m, o+1);
    }
}

int recursiveFun5(int n)
{
    for (i = 0; i < n; i += 2) {
        // do something
    }

    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun5(n-5);
}

4
Wenn Sie die Analyse nicht jedes Mal durchführen möchten, gibt es eine Black-Box-Technik namens Master-Methode. Unter der Annahme, dass alle rekursiven Teilungen von Eingaben in jedem Fall gleich groß sind.
Vivek Krishna


Antworten:


344

Die zeitliche Komplexität in Big O-Notation für jede Funktion ist in numerischer Reihenfolge:

  1. Die erste Funktion wird n-mal rekursiv aufgerufen, bevor der Basisfall erreicht wird, daher O(n)wird sie häufig als linear bezeichnet .
  2. Die zweite Funktion heißt jedes Mal n-5, also ziehen wir fünf von n ab, bevor wir die Funktion aufrufen, aber n-5 ist es auch O(n). (Wird tatsächlich als n / 5-fache Ordnung bezeichnet. Und O (n / 5) = O (n)).
  3. Diese Funktion ist log (n) Basis 5, für jedes Mal, wenn wir vor dem Aufruf der Funktion durch 5 teilen, verwendet ihre O(log(n))(Basis 5), oft logarithmisch und meistens Big O-Notation und Komplexitätsanalyse, Basis 2.
  4. Im vierten, dann ist es O(2^n), oder exponentiell , da jeder Funktionsaufruf selbst nennt zweimal es sei denn , es rekursiv wurde n - mal.
  5. Was die letzte Funktion betrifft, so nimmt die for-Schleife n / 2, da wir um 2 zunehmen, und die Rekursion n-5, und da die for-Schleife rekursiv aufgerufen wird, liegt die Zeitkomplexität in (n-5) * (n / 2) = (2n-10) * n = 2n ^ 2- 10n, aufgrund des asymptotischen Verhaltens und der Überlegungen zum Worst-Case-Szenario oder der Obergrenze, die das große O anstrebt, interessieren wir uns nur für den größten Begriff O(n^2).

    Viel Glück auf deiner Halbzeit;)


Ihr Recht um das fünfte, das n wird für die for-Schleife abnehmen, aber für das vierte denke ich nicht, dass es jedes Mal n ^ 2 ist, wenn Sie die Rekursion zweimal aufrufen, also sollte es 2 ^ n sein, plus das war Ihr antworte im Kommentar früher.
Codierer

2
@MJGwater Die Laufzeit der Schleife sei m. Wenn der rekursive Lauf 1 Mal ausgeführt wird, dauert es m, um die Schleife auszuführen. Wenn der rekursive Lauf 2 Mal ausgeführt wird, wird die Schleife auch 2 Mal ausgeführt, sodass es 2 m dauert ... und so weiter. Es ist also '*', nicht '^'.
bjc

3
@coder Die Erklärung für 5 scheint seltsam. Wenn das Inkrementieren um 2 zu n/2Iterationen der forSchleife führt, warum würde das Dekrementieren um 5 nicht zu n/5rekursiven Aufrufen führen? Dies würde immer noch zu O(n^2)einer intuitiveren Erklärung führen. Warum Subtraktion und Division mischen, wenn sie unbedingt dasselbe tun müssen?
Jack

1
@coder also für # 4, wenn es 3 rekursive Aufrufe in der Funktionsdefinition gäbe, hätte es eine Zeitkomplexität von O (3 ^ n)? Und für 5 rekursive Aufrufe wäre es O (5 ^ n), richtig?
rmutalik

1
@ Jack Ja, ich habe mich das auch gefragt. Es sollte n/5nicht sein n-5. Und letztendlich läuft das Ganze darauf hinaus O(N^2).
Anuj

128

Für den Fall, wo n <= 0, T(n) = O(1). Daher hängt die zeitliche Komplexität davon ab, wann n >= 0.

Wir werden den Fall n >= 0im folgenden Teil betrachten.

1.

T(n) = a + T(n - 1)

wo a eine Konstante ist.

Durch Induktion:

T(n) = n * a + T(0) = n * a + b = O(n)

wobei a, b eine Konstante sind.

2.

T(n) = a + T(n - 5)

wo a eine Konstante ist

Durch Induktion:

T(n) = ceil(n / 5) * a + T(k) = ceil(n / 5) * a + b = O(n)

wobei a, b eine Konstante sind und k <= 0

3.

T(n) = a + T(n / 5)

wo a eine Konstante ist

Durch Induktion:

T(n) = a * log5(n) + T(0) = a * log5(n) + b = O(log n)

wobei a, b eine Konstante sind

4.

T(n) = a + 2 * T(n - 1)

wo a eine Konstante ist

Durch Induktion:

T(n) = a + 2a + 4a + ... + 2^(n-1) * a + T(0) * 2^n 
     = a * 2^n - a + b * 2^n
     = (a + b) * 2^n - a
     = O(2 ^ n)

wobei a, b eine Konstante sind.

5.

T(n) = n / 2 + T(n - 5)

wobei n eine Konstante ist

Schreiben Sie neu, n = 5q + rwobei q und r eine ganze Zahl sind und r = 0, 1, 2, 3, 4

T(5q + r) = (5q + r) / 2 + T(5 * (q - 1) + r)

Wir haben q = (n - r) / 5, und da r <5 ist, können wir es als Konstante betrachten, alsoq = O(n)

Durch Induktion:

T(n) = T(5q + r)
     = (5q + r) / 2 + (5 * (q - 1) + r) / 2 + ... + r / 2 +  T(r)
     = 5 / 2 * (q + (q - 1) + ... + 1) +  1 / 2 * (q + 1) * r + T(r)
     = 5 / 4 * (q + 1) * q + 1 / 2 * (q + 1) * r + T(r)
     = 5 / 4 * q^2 + 5 / 4 * q + 1 / 2 * q * r + 1 / 2 * r + T(r)

Da r <4 ist, können wir eine Konstante b finden, so dass b >= T(r)

T(n) = T(5q + r)
     = 5 / 2 * q^2 + (5 / 4 + 1 / 2 * r) * q + 1 / 2 * r + b
     = 5 / 2 * O(n ^ 2) + (5 / 4 + 1 / 2 * r) * O(n) + 1 / 2 * r + b
     = O(n ^ 2)

1
Ich habe kürzlich eine Interviewfrage (und damit auch das Interview) nicht bestanden, die mit der Analyse der zeitlichen und räumlichen Komplexität einer rekursiven Fibonacci-Funktion zu tun hat. Diese Antwort ist episch und hat sehr geholfen. Ich liebe sie. Ich wünschte, ich könnte dich zweimal wählen. Ich weiß, dass es alt ist, aber haben Sie etwas Ähnliches für die Berechnung des Speicherplatzes - vielleicht einen Link, irgendetwas?
Dimitar Dimitrov

Sollte die Induktion für Nr. 4 nicht die folgende sein, obwohl das Ergebnis dasselbe ist? T (n) = a + 2T (n-1) = a + 2a + 4T (n-1) = 3a + 4a + 8T (n-1) = a * (2 ^ n - 1) + 2 ^ n * T (0) = a * (2 ^ n - 1) + b * 2 ^ n = (a + b) * 2 ^ n - a = O (2 ^ n)
Snowfish

27

Eine der besten Möglichkeiten, die Komplexität des rekursiven Algorithmus zu approximieren, ist das Zeichnen des Rekursionsbaums. Sobald Sie den rekursiven Baum haben:

Complexity = length of tree from root node to leaf node * number of leaf nodes
  1. Die erste Funktion hat die Länge nund Anzahl der Blattknoten, 1so dass die Komplexität sein wirdn*1 = n
  2. Die zweite Funktion hat wieder die Länge n/5und Anzahl der Blattknoten, 1so dass die Komplexität zunimmt n/5 * 1 = n/5. Es sollte angenähert werden ann

  3. Für die dritte Funktion wird, da nbei jedem rekursiven Aufruf durch 5 geteilt wird, die Länge des rekursiven Baums log(n)(base 5)und die Anzahl der Blattknoten wieder 1 sein, so dass die Komplexität sein wirdlog(n)(base 5) * 1 = log(n)(base 5)

  4. Für die vierte Funktion ist, da jeder Knoten zwei untergeordnete Knoten hat, die Anzahl der Blattknoten gleich (2^n)und die Länge des rekursiven Baums nso komplex (2^n) * n. Aber da nes vor unbedeutend ist (2^n), kann es ignoriert werden und Komplexität kann nur als solche bezeichnet werden (2^n).

  5. Für die fünfte Funktion gibt es zwei Elemente, die die Komplexität einführen. Komplexität, die durch die rekursive Natur der Funktion eingeführt wird, und Komplexität, die durch die forSchleife in jeder Funktion eingeführt wird. Bei der obigen Berechnung wird die Komplexität, die durch die rekursive Natur der Funktion eingeführt wird, ~ nund die Komplexität aufgrund der for-Schleife sein n. Gesamtkomplexität wird sein n*n.

Hinweis: Dies ist eine schnelle und schmutzige Methode zur Berechnung der Komplexität (nichts offizielles!). Würde gerne Feedback dazu hören. Vielen Dank.


Hervorragende Antwort! Ich habe eine Frage zur vierten Funktion. Wenn es drei rekursive Aufrufe gegeben hätte, wäre die Antwort (3 ^ n). Oder würdest du immer noch nur (2 ^ n) sagen?
Ben Forsrup

@ Shubham: # 4 scheint mir nicht richtig zu sein. Wenn die Anzahl der Blätter ist, muss 2^ndie Höhe des Baumes sein n, nicht log n. Die Höhe wäre nur, log nwenn ndie Gesamtzahl der Knoten im Baum dargestellt würde. Aber das tut es nicht.
Julian A.

@ BenForsrup: Es wird 3 ^ n sein, da jeder Knoten drei untergeordnete Knoten hat. Der beste Weg, um sicherzugehen, besteht darin, den rekursiven Baum selbst mit Dummy-Werten zu zeichnen.
Shubham

# 2 sollte n-5 sein, nicht n / 5
Fintasys

7

Wir können es mathematisch beweisen, was mir in den obigen Antworten gefehlt hat.

Es kann Ihnen dramatisch helfen, zu verstehen, wie eine Methode berechnet wird. Ich empfehle, es von oben nach unten zu lesen, um zu verstehen, wie es geht:

  1. T(n) = T(n-1) + 1Dies bedeutet, dass die Zeit, die für das Beenden der Methode benötigt wird, der gleichen Methode entspricht, jedoch mit n-1, T(n-1)und wir fügen jetzt hinzu, + 1da es die Zeit ist, die benötigt wird, um die allgemeinen Operationen abzuschließen (außer T(n-1)). Nun werden wir Folgendes feststellen T(n-1): T(n-1) = T(n-1-1) + 1. Es sieht so aus, als könnten wir jetzt eine Funktion bilden, die uns eine Art Wiederholung geben kann, damit wir sie vollständig verstehen können. Wir werden die rechte Seite T(n-1) = ...statt T(n-1)innerhalb der Methode platzieren, T(n) = ...die uns geben wird: T(n) = T(n-1-1) + 1 + 1was ist T(n) = T(n-2) + 2oder mit anderen Worten, wir müssen unsere fehlenden finden k: T(n) = T(n-k) + k. Der nächste Schritt besteht darin, zu n-kbehaupten, n-k = 1dass am Ende der Rekursion genau O (1) benötigt wird, wennn<=0. Aus dieser einfachen Gleichung wissen wir das jetzt k = n - 1. Lassen Sie uns kin unsere endgültige Methode setzen: T(n) = T(n-k) + kwas uns geben wird: T(n) = 1 + n - 1was genau noder ist O(n).
  2. Ist das gleiche wie 1. Sie können es selbst testen und sehen, dass Sie bekommen O(n).
  3. T(n) = T(n/5) + 1Nach wie vor entspricht die Zeit bis zum Abschluss dieser Methode der Zeit derselben Methode, an n/5die sie jedoch gebunden ist T(n/5). Lassen Sie uns T(n/5)wie in 1 finden: T(n/5) = T(n/5/5) + 1was ist T(n/5) = T(n/5^2) + 1. Lassen Sie uns Platz im T(n/5)Inneren T(n)für die endgültige Berechnung: T(n) = T(n/5^k) + k. Wieder wie zuvor, n/5^k = 1was n = 5^kgenau so ist, als würde man fragen, was in Potenz von 5 uns n geben wird, lautet die Antwort log5n = k(Protokoll von Basis 5). Lassen Sie uns unsere Ergebnisse T(n) = T(n/5^k) + kwie folgt einordnen: T(n) = 1 + lognWelches istO(logn)
  4. T(n) = 2T(n-1) + 1Was wir hier haben, ist im Grunde das gleiche wie zuvor, aber dieses Mal rufen wir die Methode 2 Mal rekursiv auf, also multiplizieren wir sie mit 2. Lassen Sie uns herausfinden, T(n-1) = 2T(n-1-1) + 1welche ist T(n-1) = 2T(n-2) + 1. Unser nächster Ort wie zuvor, wir setzen unsere Erkenntnis: T(n) = 2(2T(n-2)) + 1 + 1das ist T(n) = 2^2T(n-2) + 2das gibt uns T(n) = 2^kT(n-k) + k. Lassen Sie uns herausfinden k, was n-k = 1das ist k = n - 1. Lassen Sie uns kwie folgt platzieren: Das T(n) = 2^(n-1) + n - 1ist ungefährO(2^n)
  5. T(n) = T(n-5) + n + 1Es ist fast dasselbe wie 4, aber jetzt fügen wir hinzu, nweil wir eine forSchleife haben. Lassen Sie uns herausfinden, T(n-5) = T(n-5-5) + n + 1was ist T(n-5) = T(n - 2*5) + n + 1. Lassen Sie es uns platzieren: T(n) = T(n-2*5) + n + n + 1 + 1)was ist T(n) = T(n-2*5) + 2n + 2)und für das k: T(n) = T(n-k*5) + kn + k)wieder: n-5k = 1was ist n = 5k + 1das ist ungefähr n = k. Dies wird uns geben: T(n) = T(0) + n^2 + nwas ungefähr ist O(n^2).

Ich empfehle jetzt, den Rest der Antworten zu lesen, um eine bessere Perspektive zu erhalten. Viel Glück beim Gewinnen dieser großen O's :)


1

Der Schlüssel hier ist die Visualisierung des Anrufbaums. Sobald dies erledigt ist, ist die Komplexität:

nodes of the call tree * complexity of other code in the function

Der letztere Term kann genauso berechnet werden wie für eine normale iterative Funktion.

Stattdessen werden die Gesamtknoten eines vollständigen Baums als berechnet

                  C^L - 1
                  -------  , when C>1
               /   C - 1
              /
 # of nodes =
              \    
               \ 
                  L        , when C=1

Dabei ist C die Anzahl der untergeordneten Elemente jedes Knotens und L die Anzahl der Ebenen des Baums (einschließlich Wurzel).

Es ist einfach, den Baum zu visualisieren. Beginnen Sie mit dem ersten Aufruf (Stammknoten) und zeichnen Sie eine Anzahl von untergeordneten Elementen, die der Anzahl der rekursiven Aufrufe in der Funktion entspricht. Es ist auch nützlich, den an den Unteraufruf übergebenen Parameter als "Wert des Knotens" zu schreiben.

Also, in den obigen Beispielen:

  1. Der Aufrufbaum hier ist C = 1, L = n + 1. Die Komplexität der restlichen Funktion ist O (1). Daher ist die Gesamtkomplexität L * O (1) = (n + 1) * O (1) = O (n)
n     level 1
n-1   level 2
n-2   level 3
n-3   level 4
... ~ n levels -> L = n
  1. Aufrufbaum ist hier C = 1, L = n / 5. Die Komplexität der restlichen Funktion ist O (1). Daher ist die Gesamtkomplexität L * O (1) = (n / 5) * O (1) = O (n)
n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5
  1. Aufrufbaum ist hier C = 1, L = log (n). Die Komplexität der restlichen Funktion ist O (1). Daher ist die Gesamtkomplexität L * O (1) = log5 (n) * O (1) = O (log (n))
n
n/5
n/5^2
n/5^3
... ~ log5(n) levels -> L = log5(n)
  1. Aufrufbaum ist hier C = 2, L = n. Die Komplexität der restlichen Funktion ist O (1). Dieses Mal verwenden wir die vollständige Formel für die Anzahl der Knoten im Aufrufbaum, da C> 1. Daher beträgt die Gesamtkomplexität (C ^ L-1) / (C-1) * O (1) = (2 ^ n - 1 ) * O (1) = O (2 ^ n) .
               n                   level 1
      n-1             n-1          level 2
  n-2     n-2     n-2     n-2      ...
n-3 n-3 n-3 n-3 n-3 n-3 n-3 n-3    ...     
              ...                ~ n levels -> L = n
  1. Aufrufbaum ist hier C = 1, L = n / 5. Die Komplexität der restlichen Funktion ist O (n). Daher ist die Gesamtkomplexität L * O (1) = (n / 5) * O (n) = O (n ^ 2)
n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.