Informatik

Dynamische Programmierung einfach erklärt

Dynamische Programmierung einfach erklärt
Dynamische Programmierung einfach erklärt
Für Quiz, Lückentext, Lernkarten und Fortschritt ist JavaScript nötig. Alle Inhalte und Lösungen bleiben direkt lesbar.

Dynamische Programmierung (DP) löst ein großes Problem mithilfe kleinerer Teilprobleme. Sie speichert deren Ergebnisse und verwendet sie später wieder. Das spart besonders dann Zeit, wenn dieselben Teilprobleme mehrfach auftreten.

Deine Lernziele

Hake ab, was du schon kannst — und komm am Ende hierher zurück!

Wann passt dynamische Programmierung?

Stell dir vor, ein Algorithmus beantwortet dieselbe kleine Frage immer wieder. DP merkt sich die erste Antwort und ruft sie bei der nächsten Wiederholung nur noch ab.

Eine wichtige Eigenschaft sind wiederkehrende Teilprobleme:

Definition

Überlappende Teilprobleme

Während der Lösung treten dieselben Teilprobleme mehrfach auf. Ein gespeichertes Ergebnis kann deshalb mehrfach genutzt werden.

Bei Optimierungsproblemen kommt eine weitere Eigenschaft hinzu:

Definition

Optimale Teilstruktur

Bei einem Optimierungsproblem lässt sich eine optimale Gesamtlösung aus optimalen Lösungen passender Teilprobleme zusammensetzen.

Die Eigenschaften müssen zum gewählten Zustand passen. Das bloße Zerlegen eines Problems reicht nicht: Die gespeicherten Antworten müssen später tatsächlich nützlich sein. Bei Problemen wie den Fibonacci-Zahlen steht keine Optimierung im Mittelpunkt; dort genügt eine geeignete Beziehung zwischen wiederkehrenden Teilproblemen.

Teste dich
Frage 1 von 2LeichtBei welcher Beobachtung lohnt sich das Speichern besonders?
Lösung: Dasselbe Teilproblem wird mehrfach gelöst. — DP spart Rechenzeit, wenn bereits berechnete Antworten erneut gebraucht werden.
Frage 2 von 2MittelEin Optimierungsproblem lässt sich in kleine Aufgaben zerlegen. Was musst du zusätzlich prüfen?
Lösung: Ob Teilergebnisse wiederkehren und optimale Teillösungen zur Gesamtlösung beitragen. — Bei Optimierungsproblemen sind überlappende Teilprobleme und eine optimale Teilstruktur entscheidende Hinweise auf einen geeigneten DP-Ansatz.
Wie entsteht ein DP-Algorithmus?

Der wichtigste Entwurfsschritt ist der Zustand. Er beschreibt genau, welche Frage ein gespeicherter Tabellenwert beantwortet.

Gehe in dieser Reihenfolge vor:

  1. Definiere den Zustand: Was bedeutet ein Eintrag?
  2. Bestimme die Basisfälle, also direkt bekannte kleinste Fälle.
  3. Formuliere die Übergangsregel: Aus welchen kleineren Zuständen entsteht der nächste?
  4. Lege eine Reihenfolge fest, in der alle benötigten Vorgänger schon berechnet sind.
  5. Lies am Zielzustand das Ergebnis ab.
  6. Speichere bei Bedarf zusätzlich Vorgänger, wenn du nicht nur den optimalen Wert, sondern auch die konkrete Lösung brauchst.
Beispiel

Beim Rasterproblem kann ein Zustand lauten: „minimaler Preis vom Start bis zu dieser Zelle“. Für eine innere Zelle sind der Wert oberhalb und der Wert links davon die Vorgänger. Erst wenn beide bekannt sind, wird die Zelle berechnet.

Merke

Ein Tabellenfeld ist erst dann verständlich, wenn du seinen Zustand in einem vollständigen Satz erklären kannst.

Teste dich
Frage 1 von 1MittelWelche Formulierung beschreibt einen brauchbaren Zustand?
Lösung: Der Eintrag speichert die minimalen Kosten vom Start bis zu dieser Zelle. — Ein präziser Zustand nennt die gespeicherte Größe und den zugehörigen Teil des Problems.
Wie macht DP Fibonacci schneller?

Die Fibonacci-Folge beginnt mit $f_0=0$ und $f_1=1$. Danach gilt:

$$f_{i+2}=f_i+f_{i+1}$$

Eine naive rekursive Funktion berechnet für $n>1$ sowohl $f_{n-1}$ als auch $f_{n-2}$. Dabei entstehen Wiederholungen: Bei $f_4$ wird beispielsweise $f_2$ zweimal berechnet. Mit wachsendem $n$ wächst die Zahl der Aufrufe exponentiell.

Bottom-up beginnt bei den Basisfällen und füllt die Werte der Reihe nach:

Index $i$0123456
$f_i$0112358
Beispiel

Gesucht ist $f_6$.

  1. Starte mit $f_0=0$ und $f_1=1$.
  2. Addiere jeweils die beiden vorherigen Werte: $f_2=0+1=1$, $f_3=1+1=2$ und $f_4=1+2=3$.
  3. Fahre fort: $f_5=2+3=5$ und $f_6=3+5=8$.
  4. Ergebnis: Die Fibonacci-Zahl mit Index 6 ist $8$.

Jeder benötigte Wert wird genau einmal erzeugt. Die Laufzeit ist daher $O(n)$ statt exponentiell.

Für den Ergebniswert allein brauchst du nicht die ganze Tabelle. Da jeder neue Wert nur von den beiden Vorgängern abhängt, genügen zwei gespeicherte Zahlen. Das ist eine problembezogene Speicheroptimierung.

Teste dich
Frage 1 von 2LeichtWelche Werte werden direkt als Basisfälle festgelegt?
Lösung: $f_0=0$ und $f_1=1$ — Die Folge ist durch zwei Basisfälle und die Summe der beiden Vorgänger festgelegt.
Frage 2 von 2MittelWarum ist die Bottom-up-Berechnung linear?
Lösung: Jeder benötigte Index wird einmal berechnet. — Von 2 bis $n$ wird jeder Tabellenwert einmal aus zwei bekannten Vorgängern gebildet.
Top-down oder Bottom-up?

Beide Varianten verwenden gespeicherte Teilergebnisse, aber sie starten an unterschiedlichen Stellen.

VarianteStartVorgehenSpeicher
Top-down mit MemoisierungGesamtproblemRekursiv nur benötigte Teilprobleme lösen und Ergebnisse zwischenspeichernCache und Aufrufstapel
Bottom-upBasisfälleTabelle in einer gültigen Abhängigkeitsreihenfolge füllenTabelle; manchmal auf wenige Vorgänger reduzierbar

Memoisierung bedeutet: Eine rekursive Funktion prüft zuerst den Cache. Ist das Ergebnis schon vorhanden, gibt sie es direkt zurück. Andernfalls berechnet und speichert sie es.

Keine Variante ist immer überlegen. Der passende Weg hängt von den tatsächlich benötigten Zuständen, ihren Abhängigkeiten und dem Speicherbedarf ab.

Teste dich
Frage 1 von 1MittelEine rekursive Lösung mit Cache erhält eine bereits gespeicherte Anfrage. Was tut sie?
Lösung: Sie gibt das gespeicherte Ergebnis zurück. — Memoisierung verhindert die erneute Berechnung eines bekannten Zustands.
Warum kann Greedy im Raster scheitern?

Im Raster startest du oben links und gehst nur nach rechts oder unten. Jedes Feld trägt einen Kostenwert. Für den Vergleich sind zwei vollständige Pfadsummen gegeben.

Der Greedy-Weg, der jeweils das unmittelbar billigere Nachbarfeld wählt, kostet:

$$3+7+9+4+2+1+2=28$$

Der optimale Weg kostet dagegen:

$$3+10+3+1+3+1+2=23$$

Die lokal teurere frühe Wahl mit Kosten $10$ führt später zu günstigeren Feldern. Eine lokal beste Entscheidung garantiert hier also keine global beste Lösung.

Für DP speichert jede Zelle die minimalen Kosten vom Start bis zu ihr. Bei einer inneren Zelle gilt in Worten:

Zellkosten plus der kleinere gespeicherte Wert von oben oder links.

Oberste Zeile und linke Spalte haben jeweils nur einen möglichen Vorgänger und werden zuerst gefüllt. Ob der Kostenwert der Startzelle mitgezählt wird, muss für alle verglichenen Wege einheitlich festgelegt werden.

Beispiel

Eine Zelle kostet $7$. Der günstigste Weg zum oberen Vorgänger kostet $3$, zum linken $15$.

  1. Vergleiche die Vorgänger: $3<15$.
  2. Wähle den oberen Vorgänger.
  3. Addiere die Zellkosten: $3+7=10$.

Der minimale Preis bis zu dieser Zelle beträgt $10$.

Teste dich
Frage 1 von 2MittelWarum reicht die Greedy-Regel „Nimm das billigere Nachbarfeld“ nicht aus?
Lösung: Sie berücksichtigt nicht, welche späteren Felder dadurch erreichbar werden. — Eine lokale Wahl bewertet nur den nächsten Schritt; DP vergleicht vollständige beste Teilwege zu Zuständen.
Frage 2 von 2SchwerEine innere Zelle kostet $6$; oben steht $11$, links $8$. Welchen DP-Wert erhält sie?
Lösung: $14$ — Der Zustand speichert minimale Kosten: $6+\min(11,8)=6+8=14$.
Wie prüfst du einen eigenen DP-Entwurf?

Nutze diese Entscheidungshilfe:

  1. Wiederholen sich dieselben Teilfragen?
  2. Kannst du jeden Zustand eindeutig benennen?
  3. Sind die Basisfälle korrekt und direkt lösbar?
  4. Verwendet die Übergangsregel nur passende kleinere Zustände?
  5. Stehen diese Zustände beim Berechnen bereits fest?
  6. Liefert der Zielzustand wirklich die gesuchte Antwort?
  7. Ist der zusätzliche Speicher den Laufzeitgewinn wert?

DP kann viel Rechenzeit sparen, benötigt aber oft Speicher für viele Zustände. Bei sehr vielen Zuständen kann auch ein DP-Algorithmus aufwendig bleiben. Eine unpassende Zustandswahl macht die Tabelle groß oder ihre Einträge nutzlos.

Gut zu wissen

Eine DP-Tabelle kann eindimensional sein wie bei Fibonacci oder zweidimensional wie bei Rasterwegen und dem Rucksackproblem. Ihre Form folgt dem Zustand, nicht einer festen DP-Regel.

Teste dich
Frage 1 von 1SchwerEin Entwurf speichert tausende Werte, aber keiner wird ein zweites Mal benötigt. Welche Bewertung passt?
Lösung: Der Speicher bringt hier keinen Vorteil durch Wiederverwendung. — Eine Tabelle allein macht noch keinen guten DP-Algorithmus. Prüfe Nutzen, Laufzeit und Speicher gemeinsam.
Karteikasten
Karteikasten

Überlege zuerst selbst und drehe die Karte anschließend zum Prüfen um.

Alles auf einen Blick
Mindmap
  • Dynamische Programmierung
    • Voraussetzungen
      • wiederkehrende Teilprobleme
      • bei Optimierung: optimale Teilstruktur
    • Entwurf
      • Zustand und Basisfälle
      • Übergangsregel und Reihenfolge
    • Umsetzung
      • Top-down mit Memoisierung
      • Bottom-up mit Tabelle
    • Bewertung
      • Laufzeitgewinn
      • Speicherbedarf
Abschluss-Check
Teste dich
Frage 1 von 3LeichtWas ist der Kern der dynamischen Programmierung?
Lösung: Teilergebnisse speichern und bei passenden Wiederholungen nutzen — DP verbindet Problemzerlegung mit dem gezielten Wiederverwenden gespeicherter Teilergebnisse.
Frage 2 von 3MittelWelche Reihenfolge beschreibt einen tragfähigen Entwurf?
Lösung: Zustand definieren, Basisfälle festlegen, Übergang formulieren, Reihenfolge bestimmen — Erst die Bedeutung der Zustände und ihre Abhängigkeiten bestimmen, wie die Tabelle gefüllt wird.
Frage 3 von 3SchwerEine Fibonacci-Lösung speichert nur die letzten beiden Werte. Warum bleibt sie korrekt?
Lösung: Jeder neue Wert hängt nur von diesen beiden Vorgängern ab. — Speicher darf reduziert werden, wenn verworfene Werte für keine spätere Berechnung mehr gebraucht werden.

Passend dazu