Mit diesem Begriff, zu deutsch
teile und herrsche,
{Sherlock Holmes, der Meisterdetektiv in den Romanen von A. C. Doyle, ist eigentlich ein Meister der divide-and-conquer Lösungsstrategie.}
beschreibt man folgende Problemlösungsstrategie:
- Zerlege
das Problem in Teilprobleme
- Finde
die Lösungen der Teilprobleme
- Setze
die Gesamtlösung als Kombination der Lösungen
zusammen
Ist eine solche divide-and-conquer Strategie für ein Problem bekannt, so kann man sie sofort als rekursives Programm
niederschreiben,
wobei die Rekursion stoppt, wenn das Problem so trivial ist, dass die Lösung unmittelbar bekannt ist, siehe
[INFO2002].
Im Folgenden finden Sie ein abstraktes Gerüst eines divide-and-conquer Programms
(dac),
das eine bestimmte Klasse von Problemen löst: ein
(Teil-)
Problem wird in maximal zwei kleinere Teilprobleme zerlegt.
Beim dynamischen Programmieren will man verhindern, bereits gelöste Teilprobleme mehrmals berechnen zu müssen.
Um dies zu verhindern, werden verhältnismäßig, aufwendig berechnete Teillösungen in einer Tabelle zwischengespeichert.
Diese Teillösungen werden im Bedarfsfalle einfach nur aus der Tabelle gelesen, anstatt diese erneut zu berechnen.
Dabei spart man Zeit, jedoch benötigt man für die Tabelle
(Lookup−Table)
zur Zwischenspeicherung mehr Speicherplatz
(Time−Space−Trade−Off).
Im Gegensatz zu
divide-and-conquer,
welches eine
top-down-Methode
ist, ist dynamisches Programmieren eine
bottom-up-Methode.
Das heißt, um für ein Problem der Größe
n
eine Lösung zu finden, werden alle für das Problem relevanten Teilprobleme der Größe
1, 2, ..., n − 1
gelöst und diese Lösungen in einer Tabelle untergebracht.
Um jeweils eine nächstgrößere Teillösung berechnen zu können,
muss auf die bisher berechneten Tabelleneinträge zugegriffen werden.
Dynamisches Programmieren wird typischerweise zur Lösung von Optimierungsproblemen angewandt.
Die Bezeichnung
„Programmierung”
ist historisch bedingt und bezeichnet ein Verfahren, das mit einer Tabelle arbeitet.
Eine wichtige Voraussetzung zur Anwendung des Verfahrens ist, dass die optimale Lösung für ein Problem der Größe
n
im Inneren aus optimalen Teillösungen kleinerer Größe zusammengesetzt ist
(Bellmannsches Optimalitätsprinzip).
Weiterhin ist dynamisches Programmieren vor allem dann effizient einsetzbar, wenn im Spektrum der verschiedenen Teillösungen
viele davon
„überlappend”
sind.
Das hat die Konsequenz, dass die optimale Lösung eines Teilproblems, die einmal berechnet und in der Tabelle fixiert ist,
im Rahmen der Berechnung verschiedener größerer Teilprobleme immer wieder verwendet werden kann, und nicht wieder neu
berechnet werden muss.
Die Entwicklung eines Algorithmus, der auf dem Prinzip der dynamischen Programmierung beruht, erfolgt in mehreren Schritten,
beschrieben in
[ALGOKURZ]:
- Charakterisiere den Lösungsraum und die Struktur einer erwünschten optimalen Lösung.
- Definiere rekursiv, wie sich eine optimale Lösung
— und der ihr zugeordnete Wert —
aus kleineren optimalen Lösungen
— und deren Werten —
zusammensetzt.
- Konzipiere den Algorithmus in einer bottom-up Weise so, dass für
n = 1, 2, 3, ...
tabellarisch optimale Teillösungen
— und deren zugeordnete Werte —
gefunden werden.
Beim Finden einer bestimmten optimalen Teillösung der Größe
k > 1
hilft hierbei, dass bereits alle optimalen Teillösungen der Größe
< k
bereitstehen.
Ein bekanntes Beispiel für einen Algorithmus, der auf dynamischem Programmieren beruht, ist der
Cooke−Younger−Kasami−Algorithmus
zur Lösung des Wortproblems bei kontextfreien Grammatiken.
[ALGOKURZ]
Greedy-Algorithmen sind mit dem dynamischen Programmieren verwandt, jedoch einfacher.
Die Grundsituation ist dieselbe: Es geht um ein Optimierungsproblem;
es soll sukzessiv eine optimale Lösung
— in Bezug auf eine gegebene Bewertungsfunktion konstruiert werden.
Während man beim dynamischen Programmieren solche optimalen Lösungen für alle kleineren Teilprobleme konstruiert
und mit Hilfe dieser in einer Tabelle eingetragenen Daten die nächstgrößere optimale Lösung konstruiert,
verzichtet man bei Greedy auf die Buchführung mittels einer Tabelle.
Statt dessen wird der nächste Erweiterungsschritt zu einer
(hoffentlich)
optimalen Lösung lediglich aufgrund der lokal verfügbaren Information getätigt.
Nehmen wir an, es gibt eine Gewichtsfunktion
w,
die die
„Güte”
einer Lösung
(auch einer Teillösung)
misst.
Es soll eine Lösung mit maximalem w-Wert konstruiert werden.
Wir starten mit der leeren Lösung.
Schrittweise wird die bisher konstruierte Teillösung erweitert.
Wenn zur Erweiterung dieser Teillösung
k
Erweiterungsmöglichkeiten zur Verfügung stehen, die auf die vergrößerten Teillösungen
l1, l2, ..., lk
führen, so wird diejenige Teillösung
li
mit
w(li)
maximal ausgewählt.
Der Name
Greedy
(gefräßig)
erklärt sich dadurch, dass ein
Greedy−Algorithmus
nach der Methode
„Nimm immer das größte Stück”
vorgeht.
Dieses
Greedy−Prinzip
kann in vielen Fällen funktionieren und tatsächlich auf eine optimale Lösung führen
— ohne den Aufwand, der bei dynamischem Programmieren betrieben werden muss.
[ALGOKURZ]