417 Stimmen

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

Ich habe morgen eine Zwischenprüfung in Informatik 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 lösen konnte. Jede Hilfe wäre sehr willkommen und würde mir bei meinen Studien sehr helfen, vielen Dank!

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

580voto

coder Punkte 5110

Die Zeitkomplexität, in Big O Notation, für jede Funktion:


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

Diese Funktion wird n-mal rekursiv aufgerufen, bevor sie den Basisfall erreicht, so dass ihre O(n) oft genannt linear .


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

Diese Funktion wird jedes Mal mit n-5 aufgerufen, also ziehen wir fünf von n ab, bevor wir die Funktion aufrufen, aber n-5 ist auch O(n) . (Eigentlich n/5-fache Ordnung genannt. Und, O(n/5) = O(n) ).


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

Diese Funktion ist log(n) zur Basis 5, jedes Mal wenn wir durch 5 dividieren bevor wir die Funktion aufrufen, damit ihre O(log(n)) (Basis 5), oft genannt logarithmisch und in den meisten Fällen wird bei der Big-O-Notation und der Komplexitätsanalyse die Basis 2 verwendet.


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

Hier ist es O(2^n) , oder exponentiell da jeder Funktionsaufruf sich selbst zweimal aufruft, es sei denn, er wurde erneut ausgeführt n Zeiten.


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

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

Und hier dauert die for-Schleife n/2, da wir um 2 erhöhen, und die Rekursion dauert n/5, und da die for-Schleife rekursiv aufgerufen wird, ist die Zeitkomplexität daher in

(n/5) * (n/2) = n^2/10,

Aufgrund des asymptotischen Verhaltens und der Überlegungen zum Worst-Case-Szenario oder der von Big O angestrebten Obergrenze interessieren wir uns nur für den größten Term, so dass O(n^2) .


Viel Glück für deine Zwischenprüfungen ;)

150voto

nhahtdh Punkte 54526

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

Wir werden den Fall prüfen n >= 0 im folgenden Teil.

1.

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

wobei a eine Konstante ist.

Durch Induktion:

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

wobei a, b einige Konstanten sind.

2.

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

wobei a eine Konstante ist

Durch Induktion:

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

wobei a, b einige Konstanten sind und k <= 0

3.

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

wobei a eine Konstante ist

Durch Induktion:

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

wobei a, b einige Konstanten sind

4.

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

wobei 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 einige Konstanten sind.

5.

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

wobei n eine Konstante ist

Umschreiben n = 5q + r wobei q und r ganze Zahlen 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 eine Konstante betrachten, also q = 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)

46voto

Shubham Punkte 2971

Eine der besten Methoden zur Annäherung an die Komplexität eines rekursiven Algorithmus ist das Zeichnen des Rekursionsbaums. Sobald Sie den Rekursionsbaum haben:

Complexity = length of tree from root node to leaf node * number of leaf nodes
  1. Die erste Funktion wird eine Länge von n und Anzahl der Blattknoten 1 Die Komplexität wird also n*1 = n

  2. Die zweite Funktion hat die Länge von n/5 und Anzahl der Blattknoten wieder 1 Die Komplexität wird also n/5 * 1 = n/5 . Sie sollte angenähert werden zu n

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

  4. Da bei der vierten Funktion jeder Knoten zwei untergeordnete Knoten hat, ist die Anzahl der Blattknoten gleich (2^n) und die Länge des rekursiven Baums ist n Die Komplexität wird also (2^n) * n . Da aber n ist unbedeutend vor (2^n) kann sie ignoriert werden und die Komplexität kann nur als (2^n) .

  5. Bei der fünften Funktion gibt es zwei Elemente, die die Komplexität erhöhen. Die Komplexität, die durch die rekursive Natur der Funktion und die Komplexität, die durch for Schleife in jeder Funktion. Bei der obigen Berechnung beträgt die Komplexität, die durch die rekursive Natur der Funktion entsteht ~ n und Komplexität aufgrund der for-Schleife n . Die Gesamtkomplexität wird sein n*n .

Hinweis: Dies ist eine schnelle und schmutzige Methode zur Berechnung der Komplexität (nichts Offizielles!). Ich würde mich über Feedback dazu freuen. Danke!

27voto

OhadM Punkte 4297

Wir können es mathematisch beweisen, was ich in den obigen Antworten vermisst habe.

Sie kann dramatisch helfen Ihnen zu verstehen, wie man jede Methode berechnet. Ich empfehle, das Buch von vorne bis hinten durchzulesen, um zu verstehen, wie man es macht:

  1. T(n) = T(n-1) + 1 Das bedeutet, dass die Zeit, die die Methode bis zum Abschluss benötigt, der gleichen Methode entspricht, aber mit n-1, was bedeutet T(n-1) und wir fügen nun hinzu + 1 weil es sich um die Zeit handelt, die für den Abschluss der allgemeinen Vorgänge benötigt wird (außer T(n-1) ). Jetzt werden wir Folgendes finden T(n-1) wie folgt: T(n-1) = T(n-1-1) + 1 . Es sieht so aus, als ob wir jetzt eine Funktion bilden können, die uns eine Art von Wiederholung gibt, damit wir alles verstehen können. Wir werden die rechte Seite von T(n-1) = ... anstelle von T(n-1) innerhalb der Methode T(n) = ... die uns geben wird: T(n) = T(n-1-1) + 1 + 1 das ist T(n) = T(n-2) + 2 oder mit anderen Worten, wir müssen unsere fehlenden k : T(n) = T(n-k) + k . Der nächste Schritt besteht darin, die n-k und behaupten, dass n-k = 1 weil es am Ende der Rekursion genau O(1) dauert, wenn n<=0 . Aus dieser einfachen Gleichung wissen wir nun, dass k = n - 1 . Platzieren wir k in unserer endgültigen Methode: T(n) = T(n-k) + k die uns das geben wird: T(n) = 1 + n - 1 was genau der n o O(n) .
  2. Ist dasselbe wie 1. Sie können es selbst testen und sehen, dass Sie O(n) .
  3. T(n) = T(n/5) + 1 wie zuvor, die Zeit für die Beendigung dieser Methode entspricht der Zeit, die die gleiche Methode, aber mit n/5 Deshalb ist sie begrenzt auf T(n/5) . Finden wir T(n/5) wie im 1: T(n/5) = T(n/5/5) + 1 das ist T(n/5) = T(n/5^2) + 1 . Platzieren wir T(n/5) innerhalb T(n) für die endgültige Berechnung: T(n) = T(n/5^k) + k . Wieder wie zuvor, n/5^k = 1 das ist n = 5^k was genau so ist, wie die Frage, was in Potenz von 5, n ergibt, ist die Antwort log5n = k (Logarithmus zur Basis 5). Legen wir unsere Ergebnisse in T(n) = T(n/5^k) + k wie folgt: T(n) = 1 + logn das ist O(logn)
  4. T(n) = 2T(n-1) + 1 was wir hier haben, ist im Grunde dasselbe wie vorher, aber dieses Mal rufen wir die Methode rekursiv 2 Mal auf, also multiplizieren wir sie mit 2. Finden wir T(n-1) = 2T(n-1-1) + 1 das ist T(n-1) = 2T(n-2) + 1 . Unser nächster Ort wie vor, lassen Sie uns unseren Fund platzieren: T(n) = 2(2T(n-2)) + 1 + 1 das ist T(n) = 2^2T(n-2) + 2 die uns T(n) = 2^kT(n-k) + k . Finden wir k indem er behauptet, dass n-k = 1 das ist k = n - 1 . Platzieren wir k wie folgt: T(n) = 2^(n-1) + n - 1 das ist ungefähr O(2^n)
  5. T(n) = T(n-5) + n + 1 Es ist fast dasselbe wie bei 4, aber jetzt fügen wir hinzu n denn wir haben eine for Schleife. Finden wir T(n-5) = T(n-5-5) + n + 1 das ist T(n-5) = T(n - 2*5) + n + 1 . Platzieren wir es: T(n) = T(n-2*5) + n + n + 1 + 1) das ist T(n) = T(n-2*5) + 2n + 2) und für die k: T(n) = T(n-k*5) + kn + k) wieder: n-5k = 1 das ist n = 5k + 1 das ist ungefähr n = k . Dies wird uns helfen: T(n) = T(0) + n^2 + n das ist ungefähr O(n^2) .

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

14voto

higlu Punkte 433

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

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

Der letztere Term kann auf die gleiche Weise berechnet werden wie bei einer normalen iterativen Funktion.

Stattdessen werden die Gesamtknoten eines vollständigen Baums wie folgt berechnet

                  C^L - 1
                  -------  , when C>1
               /   C - 1
              /
 # of nodes =
              \    
               \ 
                  L        , when C=1 (this is special case of a single branch tree)

Dabei steht C für die Anzahl der Kinder jedes Knotens und L für die Anzahl der Ebenen des Baums (einschließlich der Wurzel).

Es ist einfach, sich den Baum vorzustellen. Beginnen Sie mit dem ersten Aufruf (Wurzelknoten) und zeichnen Sie dann eine Anzahl von Kindern ein, 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.

Hier also die Ergebnisse für die obigen Beispiele:


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

Denken Sie zunächst an den Anrufbaum:

n     level 1
n-1   level 2
n-2   level 3
n-3   level 4
... ~ n levels -> L = n

Hier ist die Anzahl der Kinder pro Knoten C = 1 und die Anzahl der Ebenen L = n+1. Die Komplexität des Rests der Funktion ist O(1). Daher ist die Gesamtkomplexität L * O(1) = (n+1) * O(1) = O(n)


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

Der Anrufbaum lautet hier:

n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5

Wiederum ist C = 1, aber L = n/5. Die Komplexität des Rests der Funktion ist O(1). Daher ist die Gesamtkomplexität L * O(1) = (n/5) * O(1) = O(n)


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

Der Anrufbaum lautet:

n
n/5
n/5^2
n/5^3
... ~ log5(n) levels -> L = log5(n)

Folglich ist C = 1, L = log(n). Die Komplexität des Rests der Funktion ist O(1). Daher ist die Gesamtkomplexität L * O(1) = log5(n) * O(1) = O(log(n))


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

Hier ist der Aufrufbaum komplexer:

               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

Hier ist die Anzahl der Kinder pro Knoten C = 2, während L = n. Die Komplexität der restlichen Funktion ist O(1). Diesmal verwenden wir die vollständige Formel für die Anzahl der Knoten im Aufrufbaum, da C > 1 ist. Daher ist die Gesamtkomplexität (C^L-1)/(C-1) * O(1) = (2^n - 1) * O(1) = O(2^n) .


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

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

Auch hier ist der Anrufbaum:

n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5

Hier ist C = 1, L = n/5. Die Komplexität des Rests der Funktion ist O(n). Daher ist die Gesamtkomplexität L * O(1) = (n/5) * O(n) = O(n^2)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X