Dynamisches Programmieren - Problemstruktur Optimale Substruktur: Optimale Lösung enthält optimale Lösungen von Teilproblemen. Bsp.: Kürzester Weg im Graphen, LCS (s. etwa Folie 42 der letzten Vorlesung) Überlappende Teilprobleme: Bei der rekursiven Berechnung von Teillösungen werden viele Teillösungen mehrfach berechnet. Bsp.: Fibonacci (Heimübungsblatt 13) 1 1
Dynamisches Programmieren - Vorgehensweise 1. Bestimme rekursive Struktur einer optimalen Lösung. 2. Entwerfe rekursive Methode zur Bestimmung des Wertes einer optimalen Lösung. 3. Transformiere rekursive Methode in eine iterative (bottom-up) Methode zur Bestimmung des Wertes einer optimalen Lösung. 4. Bestimme aus den unter 3. berechneten Informationen eine optimale Lösung. 2 2
Szenario: Maschine für W Zeitschritte zur Verfügung n Aufgaben, die von Maschine erledigt werden könnten Aufgaben benötigen unterschiedlich viel Zeit Reihenfolge und Zeitpunkt unerheblich Ziel: Sie wollen ihre Maschine möglichst gut auslasten 3 3
Beispiel: 15 Zeiteinheiten stehen insgesamt zur Verfügung Aufgabe 1 2 3 4 5 6 7 8 9 Zeit 8 3 5 2 2 4 3 11 13 4 4
Beispiel: 15 Zeiteinheiten stehen insgesamt zur Verfügung Aufgabe 1 2 3 4 5 6 7 8 9 Zeit 8 3 5 2 2 4 3 11 13 Aufgabe 2 und Aufgabe 11 benötigen 14 Zeitschritte 5 5
Beispiel: 15 Zeiteinheiten stehen insgesamt zur Verfügung Aufgabe 1 2 3 4 5 6 7 8 9 Zeit 8 3 5 2 2 4 3 11 13 Aufgabe 2 und Aufgabe 11 benötigen 14 Zeitschritte Bessere Lösung möglich? 6 6
Beispiel: 15 Zeiteinheiten stehen insgesamt zur Verfügung Aufgabe 1 2 3 4 5 6 7 8 9 Zeit 8 3 5 2 2 4 3 11 13 Aufgabe 2 und Aufgabe 11 benötigen 14 Zeitschritte Bessere Lösung möglich? Ja! Aufgabe 1,3 und 4 benötigen 15 Zeiteinheiten 7 7
Subset Sum (Optimierungsvariante): Eingabe: Menge X mit n pos. Integers und ein Zielwert W Ausgabe: Menge S X, so dass Σ x unter der Bedingung x S Σ x W maximiert wird x S Subset Sum (Entscheidungsvariante): Eingabe: Menge X mit n pos. Integers und ein Zielwert W Ausgabe: true, wenn es Menge S X mit Σ x = W gibt false, sonst 8 8
AusschöpfendeSuche(X,W) 1. for each subset S X do 2. sum 0 3. for each x S do 4. sum sum + x 5. if sum = W return true 6. return false 9 9
AusschöpfendeSuche(X,W) 1. for each subset S X do 2. sum 0 3. for each x S do 4. sum sum + x 5. if sum = W return true 6. return false Probiere alle Untermengen aus 10 10
AusschöpfendeSuche(X,W) 1. for each subset S X do 2. sum 0 3. for each x S do 4. sum sum + x 5. if sum = W return true 6. return false Berechne Summe der Elemente in S 11 11
AusschöpfendeSuche(X,W) 1. for each subset S X do 2. sum 0 3. for each x S do 4. sum sum + x 5. if sum = W return true 6. return false Falls Summe=W, dann Ausgabe true 12 12
AusschöpfendeSuche(X,W) 1. for each subset S X do 2. sum 0 3. for each x S do 4. sum sum + x 5. if sum = W return true 6. return false Falls es kein S gibt, das sich zu W aufsummiert, Ausgabe false 13 13
AusschöpfendeSuche(X,W) 1. for each subset S X do 2. sum 0 3. for each x S do 4. sum sum + x 5. if sum = W return true 6. return false Laufzeit: Wie viele Untermengen gibt es? 14 14
AusschöpfendeSuche(X,W) 1. for each subset S X do 2. sum 0 3. for each x S do 4. sum sum + x 5. if sum = W return true 6. return false Laufzeit: Wie viele Untermengen gibt es? X S 31 ja 17 nein 13 ja 20 ja 4 nein 8 ja 19 ja 15 15
AusschöpfendeSuche(X,W) 1. for each subset S X do 2. sum 0 3. for each x S do 4. sum sum + x 5. if sum = W return true 6. return false Laufzeit: Wie viele Untermengen gibt es? X S 31 1 17 0 13 1 20 1 4 0 8 1 19 1 16 16
AusschöpfendeSuche(X,W) 1. for each subset S X do 2. sum 0 3. for each x S do 4. sum sum + x 5. if sum = W return true 6. return false Laufzeit: Wie viele Untermengen gibt es? Soviel wie Bitstrings der Länge n (minus 1) X S 31 1 17 0 13 1 20 1 4 0 8 1 19 1 17 17
AusschöpfendeSuche(X,W) 1. for each subset S X do 2. sum 0 3. for each x S do 4. sum sum + x 5. if sum = W return true 6. return false Laufzeit: Wie viele Untermengen gibt es? Soviel wie Bitstrings der Länge n (minus 1) X S 31 1 17 0 13 1 20 1 Es gibt 2 viele Bitstrings der Länge n n 4 0 8 1 19 1 18 18
AusschöpfendeSuche(X,W) 1. for each subset S X do 2. sum 0 3. for each x S do 4. sum sum + x 5. if sum = W return true 6. return false Laufzeit: Laufzeit (mindestens) Ω(n 2 ) n X S 31 1 17 0 13 1 20 1 4 0 8 1 19 1 19 19
AusschöpfendeSuche(X,W) 1. for each subset S X do 2. sum 0 3. for each x S do 4. sum sum + x 5. if sum = W return true 6. return false Laufzeit: Laufzeit (mindestens) Ω(n 2 ) Wenn alle Rechner der Welt 100 Jahre rechnen würden, könnten sie mit diesem Algorithmus ein Problem mit 500 Zahlen nicht lösen n X S 31 1 17 0 13 1 20 1 4 0 8 1 19 1 20 20
Dynamisches Programmieren - Vorgehensweise 1. Bestimme rekursive Struktur einer optimalen Lösung. 2. Entwerfe rekursive Methode zur Bestimmung des Wertes einer optimalen Lösung. 21 21
RekSubsetSum(X,W,n) 1. if W=0 then return true 2. if n=0 then return false 3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1)) Aufruf: RekSubsetSum(X,W,length[X]) 22 22
RekSubsetSum(X,W,n) 1. if W=0 then return true 2. if n=0 then return false Hat X[1,,n] eine Teilmenge S mit Gesamtwert W? 3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1)) Aufruf: RekSubsetSum(X,W,length[X]) 23 23
RekSubsetSum(X,W,n) 1. if W=0 then return true 2. if n=0 then return false Die leere Menge hat Gesamtgewicht W=0. 3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1)) Aufruf: RekSubsetSum(X,W,length[X]) 24 24
RekSubsetSum(X,W,n) 1. if W=0 then return true 2. if n=0 then return false Da n=0 ist, betrachten wir nur noch die leere Menge. Keine Teilmenge dieser Menge erreicht aufsummiert den Wert W 0. 3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1)) Aufruf: RekSubsetSum(X,W,length[X]) 25 25
RekSubsetSum(X,W,n) 1. if W=0 then return true 2. if n=0 then return false 3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1)) Ansonsten: Logisches Oder von zwei Fällen Aufruf: RekSubsetSum(X,W,length[X]) 26 26
RekSubsetSum(X,W,n) 1. if W=0 then return true 2. if n=0 then return false 3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1)) S enthält X[n]: Dann müssen wir in X[1,..,n-1] eine Menge mit Gewicht W-X[n] suchen Aufruf: RekSubsetSum(X,W,length[X]) 27 27
RekSubsetSum(X,W,n) 1. if W=0 then return true 2. if n=0 then return false S enthält nicht X[n]: Dann müssen wir in X[1,..,n-1] eine Menge mit Gewicht W suchen 3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1)) Aufruf: RekSubsetSum(X,W,length[X]) 28 28
RekSubsetSum(X,W,n) 1. if W=0 then return true 2. if n=0 then return false 3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1)) Laufzeit: n Θ(2 ) 29 29
Beobachtung: Es gibt maximal length[x] W unterschiedliche Aufrufe von RekSubsetSum() 30 30
Beobachtung: Es gibt maximal length[x] W unterschiedliche Aufrufe von RekSubsetSum() length[x] Falls also length[x] W << 2 ist, dann rufen wir RekSubsetSum() häufig mit denselben Parametern auf 31 31
Beobachtung: Es gibt maximal length[x] W unterschiedliche Aufrufe von RekSubsetSum() length[x] Falls also length[x] W << 2 ist, dann rufen wir RekSubsetSum() häufig mit denselben Parametern auf Wir lösen also dasselbe (Unter-)Problem mehrfach! 32 32
Beobachtung: Es gibt maximal length[x] W unterschiedliche Aufrufe von RekSubsetSum() length[x] Falls also length[x] W << 2 ist, dann rufen wir RekSubsetSum() häufig mit denselben Parametern auf Wir lösen also dasselbe (Unter-)Problem mehrfach! Verbesserung des Algorithmus: Speichere berechnete Lösungen zwischen Falls Lösung ein zweites Mal berechnet werden soll, gib gespeicherte Lösung zurück 33 33
Beobachtung: Es gibt maximal length[x] W unterschiedliche Aufrufe von RekSubsetSum() length[x] Falls also length[x] W << 2 ist, dann rufen wir RekSubsetSum() häufig mit denselben Parametern auf Wir lösen also dasselbe (Unter-)Problem mehrfach! Verbesserung des Algorithmus: Speichere berechnete Lösungen zwischen Kernidee des dynamischen Programmierens! Falls Lösung ein zweites mal berechnet werden soll, gib gespeicherte Lösung zurück 34 34
Lösungsmatrix A A ist undef, wenn noch keine Lösung berechnet wurde A[i,j] = true, wenn es Teilmenge von X[1,..,j] gibt, deren Summe i ist A[i,j] = false, wenn es keine solche Teilmenge gibt 35 35
InitSubsetDynamic(X,W,n) 1. for i 0 to W do 2. for j 0 to n do 3. A[i,j] undef 4. for i 1 to W do 5. A[i,0] false 6. for j 0 to n do 7. A[0,j] true 6. RekSubsetDynamic(A,X,W,n) 36 36
InitSubsetDynamic(X,W,n) 1. for i 0 to W do 2. for j 0 to n do 3. A[i,j] undef 4. for i 1 to W do 5. A[i,0] false 6. for j 0 to n do 7. A[0,j] true 6. RekSubsetDynamic(A,X,W,n) Initialisiere Lösungsmatrix A 37 37
InitSubsetDynamic(X,W,n) 1. for i 0 to W do 2. for j 0 to n do 3. A[i,j] undef 4. for i 1 to W do 5. A[i,0] false 6. for j 0 to n do 7. A[0,j] true Eine Teilmenge der leeren Menge kann nicht den Wert i ergeben 6. RekSubsetDynamic(A,X,W,n) 38 38
InitSubsetDynamic(X,W,n) 1. for i 0 to W do 2. for j 0 to n do 3. A[i,j] undef 4. for i 1 to W do 5. A[i,0] false 6. for j 0 to n do 7. A[0,j] true Die leere Menge summiert sich zu 0 auf 6. RekSubsetDynamic(A,X,W,n) 39 39
InitSubsetDynamic(X,W,n) 1. for i 0 to W do 2. for j 0 to n do 3. A[i,j] undef 4. for i 1 to W do 5. A[i,0] false 6. for j 0 to n do 7. A[0,j] true 6. RekSubsetDynamic(A,X,W,n) Aufruf des eigentlichen Algorithmus 40 40
RekSubsetDynamic(A,X,W,n) 1. if A[W,n-1]=undef then 2. A[W,n-1]=RekSubsetDynamic(A,X,W,n-1) 3. if W X[n] then 4. if A[W-X[n], n-1]=undef then 5. A[W-X[n], n-1]=reksubsetdynamic(a,x,w-x[n], n-1) 6. return (A[W,n-1] or A[W-X[n],n-1]) 7. else return A[W,n-1] 41 41
RekSubsetDynamic(A,X,W,n) 1. if A[W,n-1]=undef then 2. A[W,n-1]=RekSubsetDynamic(A,X,W,n-1) 3. if W X[n] then 4. if A[W-X[n], n-1]=undef then 5. A[W-X[n], n-1]=reksubsetdynamic(a,x,w-x[n], n-1) 6. return (A[W,n-1] or A[W-X[n],n-1]) 7. else return A[W,n-1] Falls A[W,n-1] nicht bekannt ist, rechne es aus 42 42
RekSubsetDynamic(A,X,W,n) 1. if A[W,n-1]=undef then Falls A[W-X[n],n-1] definiert 2. A[W,n-1]=RekSubsetDynamic(A,X,W,n-1) 3. if W X[n] then 4. if A[W-X[n], n-1]=undef then 5. A[W-X[n], n-1]=reksubsetdynamic(a,x,w-x[n], n-1) 6. return (A[W,n-1] or A[W-X[n],n-1]) 7. else return A[W,n-1] 43 43
RekSubsetDynamic(A,X,W,n) 1. if A[W,n-1]=undef then 2. A[W,n-1]=RekSubsetDynamic(A,X,W,n-1) 3. if W X[n] then 4. if A[W-X[n], n-1]=undef then 5. A[W-X[n], n-1]=reksubsetdynamic(a,x,w-x[n], n-1) 6. return (A[W,n-1] or A[W-X[n],n-1]) 7. else return A[W,n-1] und nicht bekannt ist, rechne es aus. 44 44
RekSubsetDynamic(A,X,W,n) 1. if A[W,n-1]=undef then 2. A[W,n-1]=RekSubsetDynamic(A,X,W,n-1) 3. if W X[n] then 4. if A[W-X[n], n-1]=undef then 5. A[W-X[n], n-1]=reksubsetdynamic(a,x,w-x[n], n-1) 6. return (A[W,n-1] or A[W-X[n],n-1]) 7. else return A[W,n-1] Gib den richtigen Wert zurück. 45 45
RekSubsetDynamic(A,X,W,n) 1. if A[W,n-1]=undef then 2. A[W,n-1]=RekSubsetDynamic(A,X,W,n-1) 3. if W X[n] then 4. if A[W-X[n], n-1]=undef then 5. A[W-X[n], n-1]=reksubsetdynamic(a,x,w-x[n], n-1) 6. return (A[W,n-1] or A[W-X[n],n-1]) 7. else return A[W,n-1] Laufzeit: O(length[X] W) 46 46
Dynamisches Programmieren - Vorgehensweise 3. Transformiere rekursive Methode in eine iterative (bottom-up) Methode zur Bestimmung des Wertes einer optimalen Lösung. 4. Bestimme aus den unter 3. berechneten Informationen eine optimale Lösung. 47 47
Eine neue Implementierung: Bottom-up Berechnung (häufig einfacher) Array A[0,,W] A[i] = true, gdw. es eine Untermenge mit Wert i gibt Initialisierung: A[0]=true A[i]=false für alle i>0 Nach Initialisierung korrektes A für leere Menge 48 48
Annahme: A korrekt berechnet für X[1,,k] Wie können wir A für X[1,,k+1] bekommen? Algorithmus: Wenn A[i] = true, dann setze A[i + X[k+1]] auf true 49 49
IterativeSubsetSum(X,W) 1. n length[a] 2. A[0] true 3. for i 1 to W do 4. A[i] false 5. for j 1 to n do 6. for i W downto 0 do 7. if A[i]=true then A[i+X[j]] true 8. return A[W] 50 50
IterativeSubsetSum(X,W) 1. n length[a] 2. A[0] true 3. for i 1 to W do 4. A[i] false 5. for j 1 to n do 6. for i W downto 0 do Initialisiere A 7. if A[i]=true then A[i+X[j]] true 8. return A[W] 51 51
IterativeSubsetSum(X,W) 1. n length[a] 2. A[0] true 3. for i 1 to W do 4. A[i] false 5. for j 1 to n do 6. for i W downto 0 do 7. if A[i]=true then A[i+X[j]] true 8. return A[W] Füge alle Elemente nacheinander ein 52 52
IterativeSubsetSum(X,W) 1. n length[a] 2. A[0] true 3. for i 1 to W do 4. A[i] false 5. for j 1 to n do 6. for i W downto 0 do 7. if A[i]=true then A[i+X[j]] true 8. return A[W] Aktualisiere A 53 53
IterativeSubsetSum(X,W) 1. n length[a] 2. A[0] true 3. for i 1 to W do 4. A[i] false 5. for j 1 to n do 6. for i W downto 0 do 7. if A[i]=true then A[i+X[j]] true 8. return A[W] Rückgabe des gesuchten Wertes 54 54
IterativeSubsetSum(X,W) 1. n length[a] 2. A[0] true 3. for i 1 to W do 4. A[i] false 5. for j 1 to n do 6. for i W downto 0 do 7. if A[i]=true then A[i+X[j]] true 8. return A[W] Laufzeit: O(n W) 55 55
Satz 19.5 Die Entscheidungsvariante des Subset Sum Problems kann in O(nW) Zeit exakt gelöst werden, wobei n die Eingabegröße ist und W der Zielwert. Einschätzung des Algorithmus: Es ist kein effizienter Algorithmus für sehr großes W bekannt Ein solcher Algorithmus würde auch sehr viele andere Probleme effizient lösen 56 56
Zusammenfassung: Dynamische Programmierung vermeidet Mehrfachberechnung von Zwischenergebnissen Bei Rekursion einsetzbar Häufig einfache bottom-up Implementierung möglich Algorithmus für schwieriges Problem (subset sum) Laufzeit hängt von Eingabewert W ab Ausblick: Rucksackproblem 57 57
Dynamisches Programmieren Rucksack Das Rucksackproblem: Rucksack mit begrenzter Kapazität Objekte mit unterschiedlichem Wert und unterschiedlicher Größe Wir wollen Objekte von möglichst großem Gesamtwert mitnehmen 58
Dynamisches Programmieren Rucksack Beispiel: Rucksackgröße 6 Größe 5 2 1 3 7 4 Wert 11 5 2 8 14 9 59
Dynamisches Programmieren Rucksack Beispiel: Rucksackgröße 6 Größe 5 2 1 3 7 4 Wert 11 5 2 8 14 9 Objekt 1 und 3 passen und haben Gesamtwert 13 Optimal? 60
Dynamisches Programmieren Rucksack Beispiel: Rucksackgröße 6 Größe 5 2 1 3 7 4 Wert 11 5 2 8 14 9 Objekt 1 und 3 passen und haben Gesamtwert 13 Optimal? Objekt 2, 3 und 4 passen und haben Gesamtwert 15! 61
Dynamisches Programmieren Rucksack Das Rucksackproblem (Optimierungsversion): Eingabe: n Objekte {1,,n}; Objekt i hat ganzz. pos. Größe g[i] und Wert v[i]; Rucksackkapazität W Ausgabe: Menge S {1,,n} mit Σ g[i] W und maximalem Wert Σ v[i] i S i S 62
Dynamisches Programmieren Rucksack Herleiten einer Rekursion: Sei O optimale Lösung Bezeichne Opt(i,w) den Wert einer optimalen Lösung aus Objekten 1 bis i bei Rucksackgröße W Unterscheide, ob Objekt n in O ist: Fall 1(n nicht in O): Opt(n,W) = Opt(n-1,W) Fall 2(n in O): Opt(n,W) = v[n] + Opt(n-1,W-g[n]) 63
Dynamisches Programmieren Rucksack Erinnerung: Bezeichne Opt(i,w) den Wert einer optimalen Lösung aus Objekten 1 bis i bei Rucksackgröße W Rekursion: Opt(i,0)= 0 für 0 i n Opt(0,i)= 0 für 0 i W Wenn W<g[i], dann Opt(i,W) = Opt(i-1,W) Sonst: Opt(i,W) = max{opt(i-1,w), v[i] + Opt(i-1,W-g[i])} 64
Dynamisches Programmieren Rucksack Rekursion: Opt(i,0)= 0 für 0 i n Opt(0,i)= 0 für 0 i W Kein Objekt passt in den Rucksack Wenn W<g[i], dann Opt(i,W) = Opt(i-1,W) Sonst: Opt(i,W) = max{opt(i-1,w), v[i] + Opt(i-1,W-g[i])} 65
Dynamisches Programmieren Rucksack Rekursion: Opt(i,0)= 0 für 0 i n Opt(0,i)= 0 für 0 i W Kein Objekt steht zur Auswahl Wenn W<g[i], dann Opt(i,W) = Opt(i-1,W) Sonst: Opt(i,W) = max{opt(i-1,w), v[i] + Opt(i-1,W-g[i])} 66
Dynamisches Programmieren Rucksack Rekursion: Opt(i,0)= 0 für 0 i n Opt(0,i)= 0 für 0 i W Passt aktuelles Objekt in den Rucksack? Wenn W<g[i], dann Opt(i,W) = Opt(i-1,W) Nein! Sonst: Opt(i,W) = max{opt(i-1,w), v[i] + Opt(i-1,W-g[i])} 67
Dynamisches Programmieren Rucksack Rekursion: Opt(i,0)= 0 für 0 i n Opt(0,i)= 0 für 0 i W Wenn W<g[i], dann Opt(i,W) = Opt(i-1,W) Sonst: Opt(i,W) = max{opt(i-1,w), v[i] + Opt(i-1,W-g[i])} Sonst: Verwende Rekursion 68