Dynamisches Programmieren WS15-16
Inhaltsverzeichnis |
Leonard Mory, Frank Ehrlich, Alexander Preuss
Thematischer Einstieg und Motivation
Was ist dynamisches Programmieren?
- Kein Algorithmus, eher eine Lösungsstrategie
- Begriff kommt aus der Zeit, wo Programmieren Tabellenmethoden bezeichnete
- Optimierungsverfahren, kein Programmieren
- "Teile und Herrsche" dient als Grundlage
- Um ein umfangreiches Problem zu lösen, wird es in kleine Teilprobleme zerlegt
- Diese werden unabhängig von einander gelöst und in eine Tabelle geschrieben
- Anders als bei "Teile und Herrsche" wird bei dem Dynamischen Programmieren ein Bottom-up-Ansatz verwendet
Bottom-up
Optimalitätskriterium
Dynamisches Programmieren vs Memoizing
- beide vermeiden wiederholte Berechnung durch Speichern
- ⇒Tradeoff: Speicher statt Zeit
| Memoization | Dynamisches Programmieren |
|---|---|
| Top-Down, Depth-First Ansatz | Bottom-Up, Breadth-First Ansatz |
| Beschreibung bleibt unverändert | Beschriebung wird verändert |
| Vermeidet unnötige Berechnungen | Kann unnötige Berechnungen nicht vermeiden |
| Kann Speicher leicht verschwenden | Kann Speicher einsparen, indem unnötige Resultate vermieden werden |
| Muss prüfen ob Berechnung bereits vorliegt | keine Prüfung nötig durch neue Beschreibung |
| Komplexität hängt von Schnelligkeit des "Nachschlagens" ab | Komplexität hängt von Speichermethode ab |
Vergleich anhand Fibonacciberechnung
Rucksackproblem
Iterative Berechnung
Instanz eines 0/1-Rucksackproblems mit K = 5
| i | 1 | 2 | 3 | 4 |
| g | 2 | 3 | 4 | 5 |
| w | 3 | 7 | 2 | 9 |
Lösung
- eine Zeile 0 für den Anfangs leeren Rucksack
- Spaltenköpfe für alle möglichen Gewichtsumen von 0 bis K
- bei Gegenstand 1 darf nur dieser in betracht gezogen werden
- bei Gegenstand 2 darf der 2. und der 1. Gegenstand in betracht gezogen werden
- bei Gegenstand 3 alle 3
- bei Gegenstand 4 alle
| Gegenstand/Gewicht | 0 | 1 | 2 | 3 | 4 | 5 | |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
| 1 | 0 | 0 | 3 | 3 | 3 | 3 | |
| 2 | 0 | 0 | 3 | 7 | 7 | 10 | |
| 3 | 0 | 0 | 3 | 7 | 7 | 10 | |
| 4 | 0 | 0 | 3 | 7 | 7 | 10 |
Berechnung
Jeder Gegenstand darf nur einmal in den Rucksack
Gegenstand 0:
- für den Anfangs leeren Rucksack
Gegenstand 1:
- nur der 1. Gegenstand darf in den Rucksack -> daher 0 oder 3
Gegenstand 2:
- (Zeile:Spalte) 2:2 -> Wert 3, da g1 Wert 3 hat;
- 2:3 -> Wert 7, da g2 Wert 7 hat;
- 2:4 -> Wert 7, da 4 kg - 3 kg = 1 kg (Verlust)-> ich gucke bei 1:1 -> Wert 0 -> 7 + 0 = 7
- 2:5 -> Wert 10, 3 kg hat den Wert 7, 2 kg den Wert 3 in 1:2
Gegenstand 3:
- fängt bei 4 kg an -> vorige Werte können daher übernommen werden
- 3:4 -> Wert wäre 2, aber 2 < darüberliegende Wert 7 -> daher wird 7 genommen
- 3:5 -> g3 Wert 2, 5 kg - 4 kg = 1 kg Verlust -> bei 2:1 Wert 0 -> 2 + 0 = 2 -> 2 < 10 -> geht nicht -> Wert 10 eintragen
Gegenstand 4:
- fängt bei 5 kg an -> vorige Werte können daher übernommen werden
- 4:5 -> Wert wäre 9, aber 9 < darüber liegende Wert 10 -> Wert 10 wird eingetragen
Rekursive Berechnung ohne Memorizing
Als erstes wird die Prozedur (Quelle: [1], S. 106) ohne memoizing implementiert. Die rekursive Definition kann leicht in die Prozedur überführt werden:
- Anzahl der rekursiven Aufrufe wird ermittelt
function FIB_REK(num) {
(define gegenstaende
(list->vector
'("dummy"
(2 . 3)
(3 . 7)
(4 . 2)
(5 . 9))))
(define gewicht
(lambda(i) (car (vector-ref gegenstaende i))))
(define wert
(lambda(i) (cdr (vector-ref gegenstaende i))))
(define vector-copy
(lambda (v)
(let* ((l (vector-length v))
(v-duplikat (make-vector l)))
(do ((i 0 (+ i 1)))((= i l) v-duplikat)
(vector-set! v-duplikat i (vector-ref v i))))))
- das Ergebnis ist wie bei der iterativen Berechnung ebenfalls 10
- die Anzahl der rekursiven Aufrufe bestimmt die Zahl der zu lösenden Teilprobleme
- Deren Bearbeitungsreihenfolge wird durch den rekursiven Prozess bestimmt
- Anzahl der Aufrufe??????????????????> bei Iterativ: 20
- -> einige Tabellenwerte werden mehrfach berechnet
- Lösung des Problems: memoizing
Rekursive Berechnung mit Memorizing
- bevor eine Berechnung durchgeführt wird, wird überprüft, ob dieser Wert bereits existiert; wenn ja, dann wird dieser genommen; wenn nein, dann wird dieser berechnet und gespeichert
Mit Memoizing
Um die rekursive Prozedur (Quelle: [1], S. 107) mit memoizing zu implementieren, benötigt es nur wenige Änderungen im bereits vorhandenen Quellcode.
Als Résumé bleibt festzuhalten, dass die Ergebnisse mit denen der iterativen Prozedur übereinstimmen. Die rekursive Berechnung ohne memoizing ist selbstverständlich nicht tauglich, da durch die unnötige Mehrfachberechnung die Zahl der rekursiven Aufrufe viel zu hoch ist. Der memoizing- Methode wiederum sollte man den Vorzug vor der iterativen Methode geben, denn sie ist mindestens so schnell wie diese, ermöglicht eine direkte Ableitung der rekursiven Regeln und vermeidet die fehleranfällige Transformation in einen iterativen Bottom-up-Algorithmus.
Rundreiseproblem
Anwendung
Needleman-Wunsch-Algorithmus (Bioinformatik, Vergleich von Nukleotidsequenzen)
CYK-Algorithmus (Theoretische Informatik, Parsen von kfGs)
Berechnung der Levenshtein Distanz (Unterschiede zwischen zwei Sätzen)