wiki.neuro.tu-berlin.de

DynamischesProgammieren

Dynamisches Progammierung[1]

Dynamisches Programmieren ist ein fundamentales Konzept in der Informatik und ein äußerst effektives Verfahren zur Lösung komplexer algorithmischer Probleme. Es tritt häufig auf bei Optimierungsproblemen. Die Grundidee beruht darauf, große Probleme in kleinere, überschaubare Teilprobleme zu zerlegen. Die Ergebnisse der Teilprobleme werden zwischengespeichert, um später wiedervewendet zu werden. Dynamische Progammierung hilft besonders bei Probleme, welche Teilprobleme haben, die wiederholt mehrfach auftreten.

Das Prinzip dabei ist die Vermeidung von redundanten Berechnunge. Anstatt jede Teilaufgabe bei jedem rekursiven Aufruf neu zu berechnen , speichert man bei Dynamischer Progamminerung bereits berechnete Teillösungen in einer Tabelle oder auch Cache ab. Dies wird Memorisierung bezeichnet. Bei späteren Bedraf wird dann auf das gespeicherte Ergebnis zurückgegriffen und Laufzeit eingesparrt.

Man unterscheidet bei der Implementierung von dynamischem Programmieren grundsätzlich zwischen zwei Vorgehensweisen: dem Top-Down-Ansatz, auch bekannt als Memoisierung, und dem Bottom-Up-Ansatz, oft als Tabellierung bezeichnet.

Top-Down-Ansatz

Beim Top-Down-Ansatz wird das Problem wie in einer normalen rekursiven Lösung von oben nach unten gelöst. Dabei wird jede rekursive Funktion so erweitert, dass sie die Lösungen ihrer Teilprobleme in einer Datenstruktur (etwa einem Wörterbuch oder Array) speichert. Wenn im Verlauf der Rekursion dasselbe Teilproblem ein weiteres Mal auftritt, wird das Ergebnis direkt aus dieser Speicherstruktur gelesen. Dadurch reduziert sich die Anzahl der rekursiven Aufrufe erheblich. Ein klassisches Beispiel ist die Berechnung der Fibonacci-Zahlen. Während eine naive rekursive Lösung eine exponentielle Laufzeit hat, reduziert sich durch Memoisierung die Komplexität auf lineare Zeit.

Bottom-Up-Ansatz

Im Gegensatz dazu funktioniert der Bottom-Up-Ansatz iterativ. Dabei wird mit den einfachsten, kleinsten Teilproblemen begonnen – also den sogenannten Basisfällen – und von dort aus schrittweise die größeren Probleme gelöst, indem man sich auf bereits berechnete kleinere Ergebnisse stützt. Hierzu wird häufig eine Tabelle oder ein Array verwendet, in dem die Zwischenwerte gespeichert werden. Auch dieser Ansatz vermeidet doppelte Berechnungen und ist häufig einfacher zu verstehen und zu debuggen als rekursive Lösungen.


Während naive rekursive Lösungen oft exponentielle Laufzeiten haben, kann mit dynamischem Programmieren in vielen Fällen eine polynomielle Laufzeit erreicht werden. Allerdings hat diese Effizienzsteigerung auch ihren Preis: dynamisches Programmieren benötigt zusätzlichen Speicherplatz, um die Zwischenlösungen zu speichern. Bei großen Problemgrößen kann dies zu einem hohen Speicherverbrauch führen. Nicht jedes Problem ist geeignet für dynamisches Programmieren. Nur wenn sowohl eine überlappende Teilstruktur als auch eine optimale Substruktur vorliegen, kann das Verfahren sinnvoll angewendet werden. Wenn dies nicht der Fall ist muss auf andere Lösungsstrategien zurückgegriffen werden, wie Greedy-Algorithmen oder Backtracking.

Referenzen


  1. https://de.wikipedia.org/wiki/Dynamische_Programmierung ↩︎