Was ist dynamische Programmierung? [geschlossen]

276

Was ist dynamische Programmierung ?

Wie unterscheidet es sich von Rekursion, Memoisierung usw.?

Ich habe den Wikipedia-Artikel darüber gelesen , aber ich verstehe ihn immer noch nicht wirklich.

smac89
quelle
1
Hier ist ein Tutorial von Michael A. Trick von der CMU, das ich besonders hilfreich fand: mat.gsia.cmu.edu/classes/dynamic/dynamic.html Es ist sicherlich zusätzlich zu allen Ressourcen, die andere empfohlen haben (alle anderen Ressourcen, insbesondere CLR) und Kleinberg, Tardos sind sehr gut!). Der Grund, warum ich dieses Tutorial mag, ist, dass es fortgeschrittene Konzepte ziemlich allmählich einführt. Es ist etwas altmodisches Material, aber es ist eine gute Ergänzung zu der hier vorgestellten Liste von Ressourcen. Schauen Sie sich auch Steven Skienas Seite und Vorträge über dynamische Programmierung an: cs.sunysb.edu/~algorith/video-lectures http:
Edmon
11
Ich habe "Dynamische Programmierung" immer als verwirrenden Begriff empfunden - "Dynamisch" schlägt nicht statisch vor, aber was ist "statische Programmierung"? Und "... Programming" erinnert an "Object Oriented Programming" und "Functional Programming", was darauf hindeutet, dass DP ein Programmierparadigma ist. Ich habe nicht wirklich einen besseren Namen (vielleicht "Dynamische Algorithmen"?), Aber es ist schade, dass wir bei diesem bleiben.
dimo414
3
@ dimo414 Die "Programmierung" hier bezieht sich eher auf die "lineare Programmierung", die unter eine Klasse mathematischer Optimierungsmethoden fällt. Eine Liste anderer mathematischer Programmiermethoden finden Sie im Artikel Mathematische Optimierung .
Syockit
1
@ dimo414 "Programmierung" bezieht sich in diesem Zusammenhang auf eine tabellarische Methode, nicht auf das Schreiben von Computercode. - Coreman
user2618142
Das in cs.stackexchange.com/questions/59797/… beschriebene Problem der Kostenminimierung für Bustickets lässt sich am besten in der dynamischen Programmierung lösen.
Truthadjustr

Antworten:

210

Dynamische Programmierung ist, wenn Sie das Wissen der Vergangenheit nutzen, um die Lösung eines zukünftigen Problems zu vereinfachen.

Ein gutes Beispiel ist das Lösen der Fibonacci-Sequenz für n = 1.000.002.

Dies wird ein sehr langer Prozess sein, aber was ist, wenn ich Ihnen die Ergebnisse für n = 1.000.000 und n = 1.000.001 gebe? Plötzlich wurde das Problem leichter zu bewältigen.

Dynamische Programmierung wird häufig bei Zeichenfolgenproblemen verwendet, z. B. beim Problem der Zeichenfolgenbearbeitung. Sie lösen eine Teilmenge (n) des Problems und verwenden diese Informationen dann, um das schwierigere ursprüngliche Problem zu lösen.

Mit der dynamischen Programmierung speichern Sie Ihre Ergebnisse im Allgemeinen in einer Art Tabelle. Wenn Sie die Antwort auf ein Problem benötigen, verweisen Sie auf die Tabelle und prüfen, ob Sie bereits wissen, um was es sich handelt. Wenn nicht, verwenden Sie die Daten in Ihrer Tabelle, um sich einen Sprung in die Antwort zu geben.

Das Cormen-Algorithmus-Buch enthält ein großartiges Kapitel über dynamische Programmierung. UND es ist kostenlos bei Google Books! Schau es dir hier an.

Samoz
quelle
50
Hast du nicht nur das Auswendiglernen beschrieben?
Dreadwail
31
Ich würde sagen, Memoization ist eine Form der dynamischen Programmierung, wenn die Memoized-Funktion / -Methode rekursiv ist.
Daniel Huckstep
6
Eine gute Antwort würde nur eine Erwähnung der optimalen Unterstruktur hinzufügen (z. B. ist jede Teilmenge eines Pfades entlang des kürzesten Pfades von A nach B selbst der kürzeste Pfad zwischen den beiden Endpunkten unter der Annahme einer Abstandsmetrik, die die Dreiecksungleichung beobachtet).
Shea
5
Ich würde nicht "einfacher" sagen, aber schneller. Ein häufiges Missverständnis ist, dass dp Probleme löst, die naive Algorithmen nicht können und das nicht der Fall ist. Geht nicht um Funktionalität, sondern um Leistung.
andandandand
6
Mit Memoization können dynamische Programmierprobleme von oben nach unten gelöst werden. Das heißt, die Funktion wird aufgerufen, um den Endwert zu berechnen, und diese Funktion ruft sich selbst rekursiv auf, um die Teilprobleme zu lösen. Ohne sie können dynamische Programmierprobleme nur von unten nach oben gelöst werden.
Pranav
175

Dynamische Programmierung ist eine Technik, mit der vermieden wird, dass in einem rekursiven Algorithmus das gleiche Teilproblem mehrfach berechnet wird.

Nehmen wir das einfache Beispiel der Fibonacci-Zahlen: Finden der n- ten Fibonacci-Zahl, definiert durch

F n = F n-1 + F n-2 und F 0 = 0, F 1 = 1

Rekursion

Der offensichtliche Weg, dies zu tun, ist rekursiv:

def fibonacci(n):
    if n == 0:
        return 0
    if n == 1:
        return 1

    return fibonacci(n - 1) + fibonacci(n - 2)

Dynamische Programmierung

  • Top Down - Auswendiglernen

Die Rekursion führt viele unnötige Berechnungen durch, da eine bestimmte Fibonacci-Zahl mehrmals berechnet wird. Eine einfache Möglichkeit, dies zu verbessern, besteht darin, die Ergebnisse zwischenzuspeichern:

cache = {}

def fibonacci(n):
    if n == 0:
        return 0
    if n == 1:
        return 1
    if n in cache:
        return cache[n]

    cache[n] = fibonacci(n - 1) + fibonacci(n - 2)

    return cache[n]
  • Prost

Ein besserer Weg, dies zu tun, besteht darin, die Rekursion insgesamt zu beseitigen, indem die Ergebnisse in der richtigen Reihenfolge ausgewertet werden:

cache = {}

def fibonacci(n):
    cache[0] = 0
    cache[1] = 1

    for i in range(2, n + 1):
        cache[i] = cache[i - 1] +  cache[i - 2]

    return cache[n]

Wir können sogar konstanten Platz nutzen und nur die notwendigen Teilergebnisse auf dem Weg speichern:

def fibonacci(n):
  fi_minus_2 = 0
  fi_minus_1 = 1

  for i in range(2, n + 1):
      fi = fi_minus_1 + fi_minus_2
      fi_minus_1, fi_minus_2 = fi, fi_minus_1

  return fi
  • Wie wende ich dynamische Programmierung an?

    1. Finden Sie die Rekursion im Problem.
    2. Von oben nach unten: Speichern Sie die Antwort für jedes Teilproblem in einer Tabelle, damit Sie sie nicht neu berechnen müssen.
    3. Bottom-up: Finden Sie die richtige Reihenfolge, um die Ergebnisse auszuwerten, damit bei Bedarf Teilergebnisse verfügbar sind.

Die dynamische Programmierung funktioniert im Allgemeinen bei Problemen mit einer inhärenten Reihenfolge von links nach rechts, wie z. B. Zeichenfolgen, Bäume oder ganzzahlige Sequenzen. Wenn der naive rekursive Algorithmus nicht mehrmals dasselbe Teilproblem berechnet, hilft die dynamische Programmierung nicht weiter.

Ich habe eine Sammlung von Problemen erstellt, um die Logik besser zu verstehen: https://github.com/tristanguigue/dynamic-programing

Tristan
quelle
3
Dies ist eine großartige Antwort und die Problemsammlung auf Github ist auch sehr nützlich. Vielen Dank!
p4sh4
Nur aus Neugier, um Dinge zu klären - Ihrer Meinung nach ist eine rekursive Implementierung unter Verwendung einer Wiederholungsrelation und Memoisierung eine dynamische Programmierung?
Codor
Danke für die Erklärung. Fehlt eine Bedingung von unten nach oben: if n in cachewie im Beispiel von oben nach unten oder fehlt mir etwas?
DavidC
Verstehe ich dann richtig, dass jede Schleife, in der die bei jeder Iteration berechneten Werte in nachfolgenden Iterationen verwendet werden, ein Beispiel für dynamische Programmierung ist?
Alexey
Könnten Sie Referenzen für die von Ihnen gegebene Interpretation angeben, einschließlich der Top-Down- und Bottom-Up-Sonderfälle?
Alexey
37

Memoization ist das Speichern von vorherigen Ergebnissen eines Funktionsaufrufs (eine echte Funktion gibt bei gleichen Eingaben immer dasselbe zurück). Es macht keinen Unterschied für die algorithmische Komplexität, bevor die Ergebnisse gespeichert werden.

Rekursion ist die Methode einer Funktion, die sich selbst aufruft, normalerweise mit einem kleineren Datensatz. Da die meisten rekursiven Funktionen in ähnliche iterative Funktionen konvertiert werden können, spielt dies auch für die algorithmische Komplexität keine Rolle.

Dynamische Programmierung ist der Prozess, bei dem einfach zu lösende Teilprobleme gelöst und daraus die Antwort erstellt werden. Die meisten DP-Algorithmen befinden sich in der Laufzeit zwischen einem Greedy-Algorithmus (falls vorhanden) und einem exponentiellen Algorithmus (alle Möglichkeiten auflisten und den besten finden).

  • DP-Algorithmen könnten mit Rekursion implementiert werden, müssen es aber nicht.
  • DP-Algorithmen können nicht durch Memoisierung beschleunigt werden, da jedes Unterproblem immer nur einmal gelöst wird (oder die Funktion "Lösen" aufgerufen wird).
philomathohollic
quelle
Sehr klar ausgedrückt. Ich wünschte, Algorithmus-Instruktoren könnten dies gut erklären.
Kelly S. French
21

Es ist eine Optimierung Ihres Algorithmus, die die Laufzeit verkürzt.

Während ein Greedy-Algorithmus normalerweise als naiv bezeichnet wird , weil er möglicherweise mehrmals über denselben Datensatz ausgeführt wird, vermeidet die dynamische Programmierung diese Gefahr durch ein tieferes Verständnis der Teilergebnisse, die gespeichert werden müssen, um die endgültige Lösung zu erstellen.

Ein einfaches Beispiel ist das Durchlaufen eines Baums oder einer Grafik nur durch die Knoten, die zur Lösung beitragen würden, oder das Einfügen der bisher gefundenen Lösungen in eine Tabelle, damit Sie nicht immer wieder dieselben Knoten durchlaufen müssen.

Hier ist ein Beispiel für ein Problem, das für die dynamische Programmierung geeignet ist, von UVAs Online-Richter: Edit Steps Ladder.

Ich werde kurz auf den wichtigen Teil der Analyse dieses Problems eingehen, der aus dem Buch Programmierherausforderungen stammt. Ich schlage vor, dass Sie es sich ansehen.

Schauen Sie sich dieses Problem genau an. Wenn wir eine Kostenfunktion definieren, die uns sagt, wie weit zwei Zeichenfolgen voneinander entfernt sind, müssen wir zwei die drei natürlichen Arten von Änderungen berücksichtigen:

Ersetzung - Ändern Sie ein einzelnes Zeichen von Muster "s" in ein anderes Zeichen im Text "t", z. B. "Schuss" in "Punkt".

Einfügen - Fügen Sie ein einzelnes Zeichen in das Muster "s" ein, damit es mit dem Text "t" übereinstimmt, z. B. "vor" in "agog" ändern.

Löschen - Löschen Sie ein einzelnes Zeichen aus dem Muster "s", damit es mit dem Text "t" übereinstimmt, z. B. "Stunde" in "unser" ändern.

Wenn wir festlegen, dass jede dieser Operationen einen Schritt kostet, definieren wir den Bearbeitungsabstand zwischen zwei Zeichenfolgen. Wie berechnen wir das?

Wir können einen rekursiven Algorithmus definieren, indem wir beobachten, dass das letzte Zeichen in der Zeichenfolge entweder übereinstimmen, ersetzt, eingefügt oder gelöscht werden muss. Wenn Sie die Zeichen in der letzten Bearbeitungsoperation abschneiden, bleibt bei einer Paaroperation ein Paar kleinerer Zeichenfolgen übrig. Sei i und j das letzte Zeichen des relevanten Präfixes von bzw. t. Nach der letzten Operation gibt es drei Paare kürzerer Zeichenfolgen, die der Zeichenfolge nach einer Übereinstimmung / Ersetzung, Einfügung oder Löschung entsprechen. Wenn wir die Kosten für die Bearbeitung der drei Paare kleinerer Zeichenfolgen kennen würden, könnten wir entscheiden, welche Option zur besten Lösung führt, und diese Option entsprechend auswählen. Wir können diese Kosten durch die großartige Sache lernen, die Rekursion ist:

#define MATCH 0 /* enumerated type symbol for match */
#define INSERT 1 /* enumerated type symbol for insert */
#define DELETE 2 /* enumerated type symbol for delete */


int string_compare(char *s, char *t, int i, int j)

{

    int k; /* counter */
    int opt[3]; /* cost of the three options */
    int lowest_cost; /* lowest cost */
    if (i == 0) return(j * indel(’ ’));
    if (j == 0) return(i * indel(’ ’));
    opt[MATCH] = string_compare(s,t,i-1,j-1) +
      match(s[i],t[j]);
    opt[INSERT] = string_compare(s,t,i,j-1) +
      indel(t[j]);
    opt[DELETE] = string_compare(s,t,i-1,j) +
      indel(s[i]);
    lowest_cost = opt[MATCH];
    for (k=INSERT; k<=DELETE; k++)
    if (opt[k] < lowest_cost) lowest_cost = opt[k];
    return( lowest_cost );

}

Dieser Algorithmus ist korrekt, aber auch unglaublich langsam.

Wenn Sie auf unserem Computer laufen, dauert es einige Sekunden, um zwei Zeichenfolgen mit 11 Zeichen zu vergleichen, und die Berechnung verschwindet in einem Niemals-Niemals-Land auf etwas längerem.

Warum ist der Algorithmus so langsam? Es dauert exponentiell, weil es Werte immer wieder neu berechnet. An jeder Position in der Zeichenfolge verzweigt sich die Rekursion auf drei Arten, was bedeutet, dass sie mit einer Geschwindigkeit von mindestens 3 ^ n wächst - sogar noch schneller, da die meisten Aufrufe nur einen der beiden Indizes reduzieren, nicht beide.

Wie können wir den Algorithmus praktisch machen? Die wichtige Beobachtung ist, dass die meisten dieser rekursiven Aufrufe Dinge berechnen, die bereits zuvor berechnet wurden. Woher wissen wir? Nun, es kann nur | s | geben · | T | mögliche eindeutige rekursive Aufrufe, da nur so viele unterschiedliche (i, j) Paare als Parameter für rekursive Aufrufe dienen.

Indem wir die Werte für jedes dieser (i, j) Paare in einer Tabelle speichern, können wir vermeiden, sie neu zu berechnen, und sie einfach nach Bedarf nachschlagen.

Die Tabelle ist eine zweidimensionale Matrix m, in der jedes der | s | · | t | Zellen enthält die Kosten für die optimale Lösung dieses Teilproblems sowie einen übergeordneten Zeiger, der erklärt, wie wir zu diesem Ort gekommen sind:

typedef struct {
int cost; /* cost of reaching this cell */
int parent; /* parent cell */
} cell;

cell m[MAXLEN+1][MAXLEN+1]; /* dynamic programming table */

Die dynamische Programmierversion weist drei Unterschiede zur rekursiven Version auf.

Erstens erhält es seine Zwischenwerte mithilfe der Tabellensuche anstelle von rekursiven Aufrufen.

** Zweitens ** wird das übergeordnete Feld jeder Zelle aktualisiert, sodass wir die Bearbeitungssequenz später rekonstruieren können.

** Drittens, ** Drittens wird es mit einer allgemeineren cell()Zielfunktion instrumentiert, anstatt nur m [| s |] [| t |] .cost zurückzugeben. Dies ermöglicht es uns, diese Routine auf eine breitere Klasse von Problemen anzuwenden.

Eine ganz besondere Analyse dessen, was erforderlich ist, um die optimalsten Teilergebnisse zu erzielen, macht die Lösung zu einer "dynamischen".

Hier ist eine alternative, vollständige Lösung für dasselbe Problem. Es ist auch eine "dynamische", obwohl seine Ausführung anders ist. Ich schlage vor, dass Sie überprüfen, wie effizient die Lösung ist, indem Sie sie dem Online-Richter von UVA vorlegen. Ich finde es erstaunlich, wie effizient ein so schweres Problem angegangen wurde.

andandandand
quelle
Muss Speicher wirklich dynamisch programmiert werden? Würde ein überspringender Arbeitsaufwand einen Algorithmus nicht als dynamisch qualifizieren?
Nthalk
Sie müssen Schritt für Schritt optimale Ergebnisse sammeln , um einen Algorithmus "dynamisch" zu machen. Die dynamische Programmierung stammt aus Bellmans Arbeit im OP. Wenn Sie sagen, dass das Überspringen einer beliebigen Wortmenge eine dynamische Programmierung ist, entwerten Sie den Begriff, da jede Suchheuristik eine dynamische Programmierung wäre. en.wikipedia.org/wiki/Dynamic_programming
andandandand
12

Die Schlüsselbits der dynamischen Programmierung sind "überlappende Unterprobleme" und "optimale Unterstruktur". Diese Eigenschaften eines Problems bedeuten, dass eine optimale Lösung aus den optimalen Lösungen für seine Unterprobleme besteht. Beispielsweise weisen Probleme mit kürzesten Wegen eine optimale Unterstruktur auf. Der kürzeste Weg von A nach C ist der kürzeste Weg von A zu einem Knoten B, gefolgt vom kürzesten Weg von diesem Knoten B nach C.

Um ein Problem mit dem kürzesten Weg zu lösen, gehen Sie genauer vor:

  • Finden Sie die Abstände vom Startknoten zu jedem Knoten, der ihn berührt (z. B. von A nach B und C).
  • Finden Sie die Abstände von diesen Knoten zu den Knoten, die sie berühren (von B nach D und E und von C nach E und F).
  • Wir kennen jetzt den kürzesten Weg von A nach E: Es ist die kürzeste Summe von Ax und xE für einen Knoten x, den wir besucht haben (entweder B oder C).
  • Wiederholen Sie diesen Vorgang, bis wir den endgültigen Zielknoten erreichen

Da wir von unten nach oben arbeiten, haben wir bereits Lösungen für die Unterprobleme, wenn es darum geht, sie zu verwenden, indem wir sie auswendig lernen.

Denken Sie daran, dass dynamische Programmierprobleme sowohl überlappende Unterprobleme als auch eine optimale Unterstruktur aufweisen müssen. Das Erzeugen der Fibonacci-Sequenz ist kein dynamisches Programmierproblem. Es verwendet Memoization, weil es überlappende Unterprobleme hat, aber keine optimale Unterstruktur (weil es kein Optimierungsproblem gibt).

Nick Lewis
quelle
1
IMHO, dies ist die einzige Antwort, die im Hinblick auf die dynamische Programmierung sinnvoll ist. Ich bin neugierig, seit die Leute angefangen haben, DP mit Fibonacci-Zahlen zu erklären (kaum relevant).
Terry Li
@ TerryLi, es mag "Sinn" machen, aber es ist nicht leicht zu verstehen. Das Fibonacci-Zahlenproblem ist bekannt und leicht zu verstehen.
Ajay
5

Dynamische Programmierung

Definition

Dynamische Programmierung (DP) ist eine allgemeine Algorithmus-Entwurfstechnik zum Lösen von Problemen mit überlappenden Unterproblemen. Diese Technik wurde in den 1950er Jahren vom amerikanischen Mathematiker „Richard Bellman“ erfunden.

Schlüsselidee

Die Schlüsselidee besteht darin, Antworten auf überlappende kleinere Unterprobleme zu speichern, um eine Neuberechnung zu vermeiden.

Dynamische Programmiereigenschaften

  • Eine Instanz wird mit den Lösungen für kleinere Instanzen gelöst.
  • Die Lösungen für eine kleinere Instanz werden möglicherweise mehrmals benötigt. Speichern Sie die Ergebnisse in einer Tabelle.
  • Somit wird jede kleinere Instanz nur einmal gelöst.
  • Zusätzlicher Platz spart Zeit.
Sabir Al Fateh
quelle
4

Ich bin auch sehr neu in der dynamischen Programmierung (ein leistungsfähiger Algorithmus für bestimmte Arten von Problemen).

In den einfachsten Worten denken Sie einfach an dynamische Programmierung als rekursiven Ansatz unter Verwendung der Vorkenntnisse

Vorwissen ist hier am wichtigsten. Behalten Sie die Lösung der bereits vorhandenen Unterprobleme im Auge.

Betrachten Sie dieses grundlegendste Beispiel für dp aus Wikipedia

Finden der Fibonacci-Sequenz

function fib(n)   // naive implementation
    if n <=1 return n
    return fib(n − 1) + fib(n − 2)

Lassen Sie uns den Funktionsaufruf mit say n = 5 abbrechen

fib(5)
fib(4) + fib(3)
(fib(3) + fib(2)) + (fib(2) + fib(1))
((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
(((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))

Insbesondere wurde fib (2) dreimal von Grund auf neu berechnet. In größeren Beispielen werden viel mehr Werte von fib oder Unterproblemen neu berechnet, was zu einem exponentiellen Zeitalgorithmus führt.

Versuchen wir es jetzt, indem wir den Wert, den wir bereits in einer Datenstruktur gefunden haben, beispielsweise eine Karte, speichern

var m := map(0 → 0, 1 → 1)
function fib(n)
    if key n is not in map m 
        m[n] := fib(n − 1) + fib(n − 2)
    return m[n]

Hier speichern wir die Lösung von Unterproblemen in der Karte, falls wir sie noch nicht haben. Diese bereits berechnete Technik zum Speichern von Werten wird als Memoization bezeichnet.

Versuchen Sie schließlich für ein Problem zunächst, die Zustände zu finden (mögliche Unterprobleme und überlegen Sie sich den besseren Rekursionsansatz, damit Sie die Lösung des vorherigen Unterproblems in weitere verwenden können).

Aman Singh
quelle
Gerade Abzocke von Wikipedia. Abgestimmt !!
Solidak
3

Dynamische Programmierung ist eine Technik zum Lösen von Problemen mit überlappenden Unterproblemen. Ein dynamischer Programmieralgorithmus löst jedes Unterproblem nur einmal und speichert dann seine Antwort in einer Tabelle (Array). Vermeiden Sie die Arbeit, die Antwort jedes Mal neu zu berechnen, wenn das Unterproblem auftritt. Die Grundidee der dynamischen Programmierung lautet: Vermeiden Sie es, dasselbe Material zweimal zu berechnen, indem Sie normalerweise eine Tabelle mit bekannten Ergebnissen von Unterproblemen führen.

Die sieben Schritte bei der Entwicklung eines dynamischen Programmieralgorithmus sind wie folgt:

  1. Richten Sie eine rekursive Eigenschaft ein, die die Lösung für eine Instanz des Problems bietet.
  2. Entwickeln Sie einen rekursiven Algorithmus gemäß der rekursiven Eigenschaft
  3. Überprüfen Sie, ob dieselbe Instanz des Problems bei rekursiven Aufrufen erneut und erneut gelöst wird
  4. Entwickeln Sie einen gespeicherten rekursiven Algorithmus
  5. Siehe das Muster beim Speichern der Daten im Speicher
  6. Konvertieren Sie den gespeicherten rekursiven Algorithmus in einen iterativen Algorithmus
  7. Optimieren Sie den iterativen Algorithmus, indem Sie den Speicher nach Bedarf verwenden (Speicheroptimierung).
Adnan Qureshi
quelle
Ist 6. Convert the memoized recursive algorithm into iterative algorithmein obligatorischer Schritt? Dies würde bedeuten, dass seine endgültige Form nicht rekursiv ist?
Truthadjustr
nicht es ist nicht obligatorisch, es ist optional
Adnan Qureshi
Ziel ist es, den rekursiven Algorithmus, der zum Speichern der Daten im Speicher verwendet wird, durch eine Iteration über die gespeicherten Werte zu ersetzen, da eine iterative Lösung die Erstellung eines Funktionsstapels für jeden rekursiven Aufruf erspart.
David C. Rankin
1

Kurz gesagt, der Unterschied zwischen Rekursionsmemoisierung und dynamischer Programmierung

Dynamische Programmierung verwendet, wie der Name schon sagt, den zuvor berechneten Wert, um die nächste neue Lösung dynamisch zu erstellen

Anwendung der dynamischen Programmierung: Wenn Ihre Lösung auf einer optimalen Unterstruktur und einem überlappenden Unterproblem basiert, ist in diesem Fall die Verwendung des zuvor berechneten Werts hilfreich, damit Sie ihn nicht neu berechnen müssen. Es ist ein Bottom-up-Ansatz. Angenommen, Sie müssen fib (n) berechnen. In diesem Fall müssen Sie nur den zuvor berechneten Wert von fib (n-1) und fib (n-2) addieren.

Rekursion: Unterteilen Sie Ihr Problem grundsätzlich in kleinere Teile, um es problemlos zu lösen. Beachten Sie jedoch, dass eine Neuberechnung nicht vermieden wird, wenn derselbe Wert zuvor in einem anderen Rekursionsaufruf berechnet wurde.

Memoisierung: Grundsätzlich wird das Speichern des alten berechneten Rekursionswerts in der Tabelle als Memoisierung bezeichnet, wodurch eine Neuberechnung vermieden wird, wenn er bereits durch einen vorherigen Aufruf berechnet wurde, sodass jeder Wert einmal berechnet wird. Bevor wir berechnen, prüfen wir, ob dieser Wert bereits berechnet wurde oder nicht, wenn er bereits berechnet wurde. Dann geben wir ihn aus der Tabelle zurück, anstatt ihn neu zu berechnen. Es ist auch ein Top-Down-Ansatz

Bemühen
quelle
-2

Hier ist ein einfaches Python - Code Beispiel Recursive, Top-down,Bottom-up Ansatz für Fibonacci - Reihe:

Rekursiv: O (2 n )

def fib_recursive(n):
    if n == 1 or n == 2:
        return 1
    else:
        return fib_recursive(n-1) + fib_recursive(n-2)


print(fib_recursive(40))

Von oben nach unten: O (n) Effizient für größere Eingaben

def fib_memoize_or_top_down(n, mem):
    if mem[n] is not 0:
        return mem[n]
    else:
        mem[n] = fib_memoize_or_top_down(n-1, mem) + fib_memoize_or_top_down(n-2, mem)
        return mem[n]


n = 40
mem = [0] * (n+1)
mem[1] = 1
mem[2] = 1
print(fib_memoize_or_top_down(n, mem))

Bottom-up: O (n) Zur Vereinfachung und für kleine Eingabegrößen

def fib_bottom_up(n):
    mem = [0] * (n+1)
    mem[1] = 1
    mem[2] = 1
    if n == 1 or n == 2:
        return 1

    for i in range(3, n+1):
        mem[i] = mem[i-1] + mem[i-2]

    return mem[n]


print(fib_bottom_up(40))
0xAliHn
quelle
Der erste Fall hat KEINE Laufzeit von n ^ 2, seine Zeitkomplexität ist O (2 ^ n): stackoverflow.com/questions/360748/…
Sam
aktualisiert danke. @ Sam
0xAliHn