Datenstrukturen und Algorithmen
Christian Sohler
FG Algorithmen & Komplexität
Ergebnisse des Tests
Häufige Fehler
Unnötige Dinge:
• Aufgabenstellung nicht richtig gelesen
• Mehrfache Lösungen derselben Aufgabe
Teile & Herrsche:
• Aufruf mit 2 Unterproblemen (gibt nur Laufzeit O(n))
• Da beide Aufrufe x berechnen, benötigt man nur einen rekursiven Aufruf!
• Geometrische Reihe:
Σ
c = const für c<1 !!!i=0
∞ i
⎣n/2⎦
Häufige Fehler
Gieriger Algorithmus:
• Aufsteigend sortiert
• Sortieralgorithmus mit schlechter worst-case Laufzeit (Bubblesort, Quicksort,…) verwendet (Quicksort hat worst-case Laufzeit Θ(n²) !)
• Falsche Laufzeit für Quicksort, Mergesort,…
Dynamische Programmierung
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
Dynamische Programmierung
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
Dynamische Programmierung
Beispiel:
• 15 Zeiteinheiten stehen insgesamt zur Verfügung
• Aufgabe 2 und Aufgabe 11 benötigen 14 Zeitschritte
Aufgabe 1 2 3 4 5 6 7 8 9
Zeit 8 3 5 2 2 4 3 11 13
Dynamische Programmierung
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
Dynamische Programmierung
Beispiel:
• 15 Zeiteinheiten stehen insgesamt zur Verfügung
• Aufgabe 2 und Aufgabe 11 benötigen 14 Zeitschritte
• Bessere Lösung möglich?
• Ja! Aufgabe 1,3 und 4 benötigen 15 Zeiteinheiten
Aufgabe 1 2 3 4 5 6 7 8 9
Zeit 8 3 5 2 2 4 3 11 13
Dynamische Programmierung
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 ≤ W maximiert wirdx∈S
x∈S
Dynamische Programmierung
Subset Sum (Entscheidungsvariante):
• Eingabe: Menge X mit n pos. Integers und ein Zielwert W
• Ausgabe: true, wenn es Menge S⊆X mit
Σ
x = W gibtfalse, sonst x∈S
Dynamische Programmierung
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
Dynamische Programmierung
AusschöpfendeSuche(X,W)
1. for each subset S⊆X do
Probiere alle Untermengen
aus
2. sum ← 0
3. for each x ← S do 4. sum ← sum + x
5. if sum = W return true 6. return false
Dynamische Programmierung
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
Dynamische Programmierung
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
Dynamische Programmierung
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
Dynamische Programmierung
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:
Dynamische Programmierung
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
X S
31 ja 17 nein 13 ja 20 ja
Dynamische Programmierung
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:
X S
31 1
17 0
13 1
20 1
4 0
8 1
19 1
Dynamische Programmierung
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
X S
31 1
17 0
13 1
20 1
Dynamische Programmierung
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:
X S
31 1
17 0
13 1
20 1
4 0
8 1
19 1
Es gibt 2 viele Bitstrings der Länge n
n
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
X S
31 1
17 0
13 1
20 1
Dynamische Programmierung
Dynamische Programmierung
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:
X S
31 1
17 0
13 1
20 1
4 0
8 1
19 1
Wenn alle Rechner der Welt 100 Jahre rechnen würden,
könnten sie mit diesem Algorithmus ein Problem mit
500 Zahlen nicht lösen
Dynamische Programmierung
RekSubsetSum(X,W,n)
1. if W=0 then return true 2. if n=0 return false
3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1))
Dynamische Programmierung
RekSubsetSum(X,W,n)
1. if W=0 then return true 2. if n=0 return false
3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1))
Aufruf:
• RekSubsetSum(X,W,length[X])
Hat X[1,…,n] eine Teilmenge S mit Gesamtwert W?
RekSubsetSum(X,W,n)
1. if W=0 then return true 2. if n=0 return false
3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1))
Dynamische Programmierung
Die leere Menge hat GesamtgewichtW=0.
RekSubsetSum(X,W,n)
1. if W=0 then return true 2. if n=0 return false
3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1))
Aufruf:
• RekSubsetSum(X,W,length[X])
Dynamische Programmierung
Da n=0 ist, betrachten wir nur noch die leere Menge.
Keine Teilmenge dieser Menge ist aufsummiert W≠0.
Dynamische Programmierung
RekSubsetSum(X,W,n)
1. if W=0 then return true 2. if n=0 return false
3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1))
Ansonsten:
Logisches oder von zwei Fällen
Dynamische Programmierung
RekSubsetSum(X,W,n)
1. if W=0 then return true 2. if n=0 return false
3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1))
Aufruf:
• RekSubsetSum(X,W,length[X])
S enthält X[n]:
Dann müssen wir in X[1,..,n-1]
eine Menge mit Gewicht W-X[n]
suchen
Dynamische Programmierung
RekSubsetSum(X,W,n)
1. if W=0 then return true 2. if n=0 return false
3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1))
S enthält nicht X[n]:
Dann müssen wir in X[1,..,n-1] eine Menge mit Gewicht W suchen
Dynamische Programmierung
RekSubsetSum(X,W,n)
1. if W=0 then return true 2. if n=0 return false
3. return (RekSubsetSum(X,W-X[n],n-1) or RekSubsetSum(X,W,n-1))
Laufzeit:
• Θ(2 )n
Dynamische Programmierung
Beobachtung:
• Es gibt maximal length[X]⋅W unterschiedliche Aufrufe von RekSubsetSum()
Dynamische Programmierung
Beobachtung:
• Es gibt maximal length[X]⋅W unterschiedliche Aufrufe von RekSubsetSum()
• Falls also length[X] ⋅ W << 2 ist, dann rufen wir RekSubsetSum() häufig mit denselben Parametern auf
length[X]
Dynamische Programmierung
Beobachtung:
• Es gibt maximal length[X]⋅W unterschiedliche Aufrufe von RekSubsetSum()
• 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!
length[X]
Dynamische Programmierung
Beobachtung:
• Es gibt maximal length[X]⋅W unterschiedliche Aufrufe von RekSubsetSum()
• 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
length[X]
Dynamische Programmierung
Beobachtung:
• Es gibt maximal length[X]⋅W unterschiedliche Aufrufe von RekSubsetSum()
• 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!
length[X]
Kernidee des dynamischen Programmierens!
Dynamische Programmierung
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
Dynamische Programmierung
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
Dynamische Programmierung
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
Dynamische Programmierung
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
Eine Teilmenge der leeren Menge kann
nicht
Dynamische Programmierung
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)
Die leere Menge summiert sich zu 0
auf
Dynamische Programmierung
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 Aufruf des
Dynamische Programmierung
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]
Dynamische Programmierung
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])
Falls A[W,n-1] nicht bekannt ist, rechne
es aus
Dynamische Programmierung
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-X[n],n-1] definiert…
Dynamische Programmierung
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])
Dynamische Programmierung
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.
Dynamische Programmierung
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])
Dynamische Programmierung
Eine neue Implementierung:
• Bottom-up Berechnung (häufig einfacher)
• Matrix 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
Dynamische Programmierung
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
Dynamische Programmierung
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]
Dynamische Programmierung
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
Dynamische Programmierung
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
Dynamische Programmierung
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
Aktualisiere A
Dynamische Programmierung
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
Dynamische Programmierung
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
Dynamische Programmierung
Satz 19
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 (z.B. TSP)
Dynamische Programmierung
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