Dynamische Programmierung



Ähnliche Dokumente
Dynamische Programmierung

Dynamische Programmierung

19. Dynamic Programming I

Hallo Welt für Fortgeschrittene

Wiederholung. Divide & Conquer Strategie

Algorithmen und Datenstrukturen 2

Optimierung. Algorithmik III Algorithmen und Modelle für kontinuierliche Datenstrukturen. Vorgehen: Dynamische Programmierung

19. Dynamic Programming I

Datenstrukturen & Algorithmen

Algorithmen und Datenstrukturen 1 Kapitel 3

Top-down Bottom-up Divide & Conquer Dynamisches Programmieren Caching (Memoization) Branch-and-Bound Greedy

Dynamische Programmierung

Algorithmen und Komplexität

19. Dynamic Programming I

Dynamische Programmierung

12. Rekursion Grundlagen der Programmierung 1 (Java)

Einführung in die Objektorientierte Programmierung Vorlesung 17: Dynamische Programmierung. Sebastian Küpper

Algorithmen und Datenstrukturen

Ein Dieb raubt einen Laden aus; um möglichst flexibel zu sein, hat er für die Beute nur einen Rucksack dabei

Algorithmen I. Tutorium Sitzung. Dennis Felsing

Dynamische Programmierung II

Dynamische Programmierung. Problemlösungsstrategie der Informatik

11. Übung Algorithmen I

Hallo Welt für Fortgeschrittene

Datenstrukturen. Mariano Zelke. Sommersemester 2012

Algorithmik Übung 3 Prof. Dr. Heiner Klocke. Sortierfolge nach Werten: Bube Dame König As nach Farben: Karo ( ) Herz ( ) Piek ( ) Kreuz ( )

Entscheidungsbäume. Definition Entscheidungsbaum. Frage: Gibt es einen Sortieralgorithmus mit o(n log n) Vergleichen?

Algorithmen und Komplexität Lösungsvorschlag zu Übungsblatt 8

Algorithmen und Datenstrukturen

5.4 Das Rucksackproblem

21. Greedy Algorithmen. Aktivitätenauswahl, Fractional Knapsack Problem, Huffman Coding Cormen et al, Kap. 16.1, 16.3

Übung zu Algorithmen und Datenstrukturen (für ET/IT)

Programmierung 2. Dynamische Programmierung. Sebastian Hack. Klaas Boesche. Sommersemester

Effiziente Algorithmen und Datenstrukturen I. Kapitel 10: Lineare Algebra

Satz 90 Sei A = (Q, Σ, δ, q 0, F ) ein DFA. Der Zeitaufwand des obigen Minimalisierungsalgorithmus ist O( Q 2 Σ ).

Präfix-Summe. Das Schweizer Offiziersmesser der Parallelen Algorithmen. Parallele Rechenmodelle Präfix-Summe Brents Lemma Anwendungen

Informatik II Dynamische Programmierung

Schulmethode zur Multiplikation von n-stelligen Binärzahlen a und b: (evtl. fallen Zeilen weg, wenn das zugehörige Bit des Multiplikators 0 ist).

9. Rekursion. 1 falls n 1 n (n 1)!, andernfalls. Experiment: Die Türme von Hanoi. Links Mitte Rechts. Mathematische Rekursion

11 Dynamisches Programmieren

Dynamische Programmierung

Einleitung Grundlagen spez. Zahlenfolgen Zusammenfassung Kombinatorik. im Rahmen Hallo Welt für Fortgeschrittene. Johannes Simon

Projekt Systementwicklung

G. Zachmann Clausthal University, Germany Die wichtigsten Entwurfsverfahren für Algorithmen:

Algorithmen und Datenstrukturen

Dynamisches Programmieren - Problemstruktur

Arithmetik und Algebra

Datenstrukturen und Algorithmen

Algorithmen & Komplexität

6. Algorithmen auf Zeichenketten

Algorithmen und Datenstrukturen in der Bioinformatik Erstes Übungsblatt WS 05/06 Musterlösung

Institut fu r Informatik

Algorithmen auf Sequenzen

Gierige Algorithmen. Seminar Hallo Welt! für Fortgeschrittene. Daniel Ziegler Daniel Ziegler Gierige Algorithmen

Datenstrukturen und Algorithmen. Christian Sohler FG Algorithmen & Komplexität

Klausur Informatik 1 SS 08. Aufgabe Max. Punkte Punkte. Gesamtpunkte:

C++ Teil 5. Sven Groß. 16. Nov Sven Groß (IGPM, RWTH Aachen) C++ Teil Nov / 16

Divide & Conquer. Problem in Teilprobleme aufteilen Teilprobleme rekursiv lösen Lösung aus Teillösungen zusammensetzen

Knuth Morris Pratt Algorithmus

Folgen und Funktionen in der Mathematik

3.3 Optimale binäre Suchbäume

Einführung in die Informatik I

Datenstrukturen und Algorithmen

Dynamische Programmierung Matrixkettenprodukt

3.2. Divide-and-Conquer-Methoden

Lösungsvorschlag Serie 2 Rekursion

16. All Pairs Shortest Path (ASPS)

II.3.1 Rekursive Algorithmen - 1 -

Algebraische und arithmetische Algorithmen

Rekursive Funktionen und ihre programmtechnische Umsetzung

Übung Algorithmen und Datenstrukturen

UE Algorithmen und Datenstrukturen 1 UE Praktische Informatik 1. Übung 5. Asymptotische Laufzeitkomplexität Definition Regeln Beispiele

Komplexität von Algorithmen

Universität München, Hans-Peter Kriegel und Thomas Seidl Informatik II a[0] a[1] a[2] a[3] a[n 1]

Fallstudie: Online-Statistik

Datenstrukturen und Algorithmen (SS 2013)

Rekursive Funktionen

Programmieren in C. Rekursive Strukturen. Prof. Dr. Nikolaus Wulff

Transkript:

Dynamische Programmierung Julian Brost 11. Juni 2013 Julian Brost Dynamische Programmierung 11. Juni 2013 1 / 39

Gliederung 1 Was ist dynamische Programmierung? Top-Down-DP Bottom-Up-DP 2 Matrix-Kettenmultiplikation 3 Longest Common Subsequence 4 Longest Increasing Subsequence 5 Zusammenfassung Julian Brost Dynamische Programmierung 11. Juni 2013 2 / 39

Fibonacci-Folge F 0 = 0 F 1 = 1 F n = F n 1 + F n 2 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,... Julian Brost Dynamische Programmierung 11. Juni 2013 3 / 39

Fibonacci-Folge F 6 F 4 F 5 F 2 F 3 F 3 F 4 F 0 F 1 F 1 F 2 F 1 F 2...... F 0 F 1 F 0 F 1 Julian Brost Dynamische Programmierung 11. Juni 2013 4 / 39

Fibonacci (naiv) O(2 n ) long long fib_naiv(int n) { if (n == 0) return 0; if (n == 1) return 1; return fib_naiv(n-1) + fib_naiv(n-2); Julian Brost Dynamische Programmierung 11. Juni 2013 5 / 39

Das geht besser! Dynamische Programmierung: Verfahren zur Lösung von (Optimierungs-)Problemen Berechnung der Gesamtlösung unter effizienter Verwendung überlappender Teillösungen Eingeführt durch Richard Bellman Julian Brost Dynamische Programmierung 11. Juni 2013 6 / 39

Top-Down-DP Idee: Rekursive Implementierung merkt sich bereits berechnete Teilergebnisse in einem Array (Memoization) Bei Aufruf der Funktion: Überprüfen, ob der Wert bereits berechnet wurde Falls ja: Keine erneute Berechnung sondern direktes Zurückgeben des bekannten Werts Ansonsten: Berechnen, Speichern und Zurückgeben des Werts Julian Brost Dynamische Programmierung 11. Juni 2013 7 / 39

Fibonacci (Top-Down) O(n) #define UNKNOWN 0 long long table[max_n] = {; // initialisiert // auf UNKNOWN long long fib_top_down(int n) { if (n == 0) return 0; if (n == 1) return 1; if (table[n] == UNKNOWN) // noch nicht berechnet table[n] = fib_top_down(n-1) + fib_top_down(n-2); return table[n]; Julian Brost Dynamische Programmierung 11. Juni 2013 8 / 39

Bottom-Up-DP Idee: Teilprobleme sortieren und iterativ lösen Bei jedem Schritt: Lösen des aktuellen Problems durch Zugriff auf bereits berechnete Teillösungen (direkter Arrayzugriff statt rekursiver Aufruf) Julian Brost Dynamische Programmierung 11. Juni 2013 9 / 39

Fibonacci (Bottom-Up) O(n) long long fib_bottom_up(int n) { long long table[n+2]; table[0] = 0; table[1] = 1; for (int i = 2; i <= n; i++) { table[i] = table[i-1] + table[i-2]; return table[n]; Julian Brost Dynamische Programmierung 11. Juni 2013 10 / 39

Fibonacci: Ausführungszeit Naiv fib naiv(50) 1m 54.688s Top-Down fib top down(50) 0.003s Bottom-Up fib bottom up(50) 0.003s Julian Brost Dynamische Programmierung 11. Juni 2013 11 / 39

Top-Down vs. Bottom-Up Top-Down Pro Einfachere Implementierung Nur tatsächlich benötigte Teillösungen werden berechnet Contra Overhead durch Rekursion Beschränkte Rekursionstiefe beim ICPC (Stacklimit) Julian Brost Dynamische Programmierung 11. Juni 2013 12 / 39

Top-Down vs. Bottom-Up Bottom-Up Pro Kein Overhead durch Rekursion Gleiche asymptotische Laufzeit, aber geringerer konstanter Faktor Contra Komplizierter zu Implementieren Evtl. Berechnung nicht benötigter Teillösungen Julian Brost Dynamische Programmierung 11. Juni 2013 13 / 39

Matrix-Kettenmultiplikation Julian Brost Dynamische Programmierung 11. Juni 2013 14 / 39

Matrix-Kettenmultiplikation Matrizen A 1, A 2, A 3,..., A n gegeben Wir wollen A 1 A 2 A 3... A n möglichst effizient, d.h. mit möglichst wenigen skalaren Multiplikationen berechnen Matrizenmultiplikation ist assoziativ, Optimierung über Klammerung möglich Julian Brost Dynamische Programmierung 11. Juni 2013 15 / 39

Matrix-Kettenmultiplikation Wir definieren die Kosten für die Multiplikation zweier Matrizen A und B als Anzahl der notwendigen Skalaren Multiplikationen: cost(a, B) = A.rows A.cols B.cols Julian Brost Dynamische Programmierung 11. Juni 2013 16 / 39

Matrix-Kettenmultiplikation Ein Beispiel: A R 10 100 B R 100 5 C R 5 50 ((AB)C) cost(a, B) + cost(ab, C) = 7500 (A(BC)) cost(b, C) + cost(a, BC) = 75000 Julian Brost Dynamische Programmierung 11. Juni 2013 17 / 39

Matrix-Kettenmultiplikation A i..j := A i A i+1... A j, i j Falls i = j: trivial, keine Multiplikationen notwendig, also keine Kosten Sonst: Wir teilen das Produkt an einer Stelle k (i k < j): A i..k = A i..k A k+1..j Kosten ergeben sich durch die Kosten für die Berechnung von A i..k, A k+1..j sowie den Kosten für die Multiplikation dieser Beiden Matrizen Wir suchen in jedem Schritt ein k, sodass diese Kosten minimal werden Julian Brost Dynamische Programmierung 11. Juni 2013 18 / 39

Matrix-Kettenmultiplikation Sei m[i, j] die Anzahl der notwendigen skalaren Multiplikationen Wir suchen dann (insgesamt) m[1, n] { 0 falls i = j m[i, j] = min i..k, A k+1..j ) i k<j falls i < j Julian Brost Dynamische Programmierung 11. Juni 2013 19 / 39

Matrix-Kettenmultiplikation O(N 3 ) Matrix matrices[max_n] = { /*... */ ; int m[max_n][max_n]; int matrix_chain(int N) { for (int i = 0; i < N; i++) { m[i][i] = 0; for (int l = 2; l <= N; l++) { for (int i = 0; i <= N - l; i++) { int j = i + l - 1; m[i][j] = INT_MAX; for (int k = i; k < j; k++) { int q = m[i][k] + m[k+1][j] + matrices[i].rows * matrices[k].cols * matrices[j].cols; if (q < m[i][j]) { m[i][j] = q; return m[0][n-1]; Julian Brost Dynamische Programmierung 11. Juni 2013 20 / 39

Matrix-Kettenmultiplikation Um eine optimale Klammerung zu rekonstruieren: Zusätzliches Array s[i, j] speichert jeweils den Parameter k, für den die Teilung von A i..j optimal war. Mit diesen Informationen kann eine Funktion dann rekursiv jeweils eine optimale Klammerung für A i..k und A k+1..j ausgeben. Julian Brost Dynamische Programmierung 11. Juni 2013 21 / 39

Matrix-Kettenmultiplikation int s[max_n][max_n]; // (+) int matrix_chain(int N) { for (int i = 0; i < N; i++) { m[i][i] = 0; for (int l = 2; l <= N; l++) { for (int i = 0; i <= N - l; i++) { int j = i + l - 1; m[i][j] = INT_MAX; for (int k = i; k < j; k++) { int q = m[i][k] + m[k+1][j] + matrices[i].rows * matrices[k].cols * matrices[j].cols; if (q < m[i][j]) { m[i][j] = q; s[i][j] = k; // (+) return m[0][n-1]; Julian Brost Dynamische Programmierung 11. Juni 2013 22 / 39

Matrix-Kettenmultiplikation void print_optimal_parens(int i, int j) { if (i == j) { cout << " " << i << " "; else { cout << "("; int k = s[i][j]; print_optimal_parens(i, k); print_optimal_parens(k+1, j); cout << ")"; Aufruf: print optimal parens(0, N-1); Julian Brost Dynamische Programmierung 11. Juni 2013 23 / 39

Longest Common Subsequence Julian Brost Dynamische Programmierung 11. Juni 2013 24 / 39

Longest Common Subsequence Seien X = {x 1, x 2,..., x m und Y = {y 1, y 2,..., y n Folgen, sowie Z = {z 1, z 2,..., z k eine LCS von X und Y. 1 Wenn x m = y n, dann z k = x m = y n und Z k 1 ist LCS von X m 1 und Y n 1. 2 Wenn x m y n, dann impliziert z k x m, dass Z eine LCS von X m 1 und Y ist. 3 Wenn x m y n, dann impliziert z k y n, dass Z eine LCS von X und Y n 1 ist. Julian Brost Dynamische Programmierung 11. Juni 2013 25 / 39

Longest Common Subsequence Sei c[i, j] die Länge einer LCS von X i und Y j Wir suchen dann (insgesamt) c[m, n] 0 falls i = 0 oder j = 0 c[i, j] = c[i 1, j 1] + 1 falls i, j > 0 und x i = y j max(c[i, j 1], c[i 1, j]) falls i, j > 0 und x i y j Julian Brost Dynamische Programmierung 11. Juni 2013 26 / 39

LCS O(MN) string X = /*... */, Y = /*... */ ; int lcs[max_m+1][max_n+1]; int LCS() { int m = X.size(); int n = Y.size(); for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (X[i-1] == Y[j-1]) { lcs[i][j] = lcs[i-1][j-1] + 1; else { lcs[i][j] = max(lcs[i-1][j], lcs[i][j-1]); return lcs[m][n]; Julian Brost Dynamische Programmierung 11. Juni 2013 27 / 39

Longest Common Subsequence Um eine LCS zu rekonstruieren: Keine Speicherung von weiteren Informationen notwendig, da sich die Entscheidung in O(1) aus X[i-1], Y[j-1], lcs[i-1][j] und lcs[i][j-1] rekonstruieren lässt (alternativ aber trotzdem möglich). Julian Brost Dynamische Programmierung 11. Juni 2013 28 / 39

Longest Common Subsequence void print_lcs(int i, int j) { if (i == 0 j == 0) { return; if (X[i-1] == Y[j-1]) { print_lcs(i-1, j-1); cout << X[i-1]; else if (lcs[i-1][j] >= lcs[i][j-1]) { print_lcs(i-1, j); else { print_lcs(i, j-1); Aufruf: print LCS(m, n); 1 1 m = X.size(), n = Y.size() Julian Brost Dynamische Programmierung 11. Juni 2013 29 / 39

Longest Increasing Subsequence Julian Brost Dynamische Programmierung 11. Juni 2013 30 / 39

Longest Increasing Subsequence Vorgehensweise: Wir fügen alle Folgenglieder der Reihe nach sortiert in ein Array (hier: minval) ein, d.h. 1 wir suchen den ersten Wert im Array, der größer oder gleich dem aktuellen Folgenglied ist und ersetzen ihn durch dieses 2 falls kein solcher Wert im Array existiert (d.h. alle Werte kleiner sind), fügen wir den Wert am Ende an Im Fall (2) haben wir eine längere LIS gefunden und erhöhen die Variable maxlen Julian Brost Dynamische Programmierung 11. Juni 2013 31 / 39

LIS O(N log N) int seq[max_n] = { /*... */ ; // Zu betrachtende Folge int minval[max_n]; int LIS(int N) { int maxlen = 0; for (int i = 0; i < N; i++) { int lis = lower_bound(minval, minval + maxlen, seq[i]) - minval + 1; if (lis > maxlen) { maxlen = lis; minval[lis-1] = seq[i]; else if (seq[i] < minval[lis-1]) { minval[lis-1] = seq[i]; return maxlen; Julian Brost Dynamische Programmierung 11. Juni 2013 32 / 39

Longest Increasing Subsequence Um eine LIS zu rekonstruieren: Speichern des Index in minval statt des Werts Zusätzliches Array, um jeweils den Vorgänger zu speichern Dieses lässt sich dann von hinten durchgehen, um eine LIS zu rekonstruieren Julian Brost Dynamische Programmierung 11. Juni 2013 33 / 39

Longest Increasing Subsequence int previd[maxn]; int maxid = -1; int LIS(int N) { int maxlen = 0; for (int i = 0; i < N; i++) { int lis = lower_bound(minval, minval + maxlen, i, // (*) [](int x, int y){ return seq[x] < seq[y]; ) - minval + 1; // (*) if (lis == 1) previd[i] = -1; // (+) else previd[i] = minval[lis-2]; // (+) if (lis > maxlen) { maxlen = lis; minval[lis-1] = i; maxid = i; // (+) else if (seq[i] < seq[minval[lis-1]]) { minval[lis-1] = i; return maxlen; Julian Brost Dynamische Programmierung 11. Juni 2013 34 / 39

Longest Increasing Subsequence void print_lis(int i) { if (i < 0) return; print_lis(previd[i]); cout << seq[i] << " "; Aufruf: print LIS(maxid); Julian Brost Dynamische Programmierung 11. Juni 2013 35 / 39

Zusammenfassung Julian Brost Dynamische Programmierung 11. Juni 2013 36 / 39

Wann verwende ich DP? Optimierungsprobleme mit Optimaler Substruktur Das Problem lässt sich in kleinere Teilprobleme zerlegen Eine optimale Gesamtlösung lässt sich aus optimalen Teillösungen zusammensetzen Überlappende Teilprobleme Ein einfacher rekursiver Algorithmus würde die gleichen Teilprobleme immer wieder lösen Durch Speichern der Teilergebnisse wird die Laufzeit massiv verbessert Julian Brost Dynamische Programmierung 11. Juni 2013 37 / 39

Wie verwende ich DP? 1 Überprüfen, ob DP anwendbar ist (siehe vorherige Folie) 2 Rekursive Lösung finden 3 Umsetzung mit DP (bottom-up oder top-down) 4 Rekonstruieren einer Lösung (falls erforderlich) Julian Brost Dynamische Programmierung 11. Juni 2013 38 / 39

Quellen Thomas H. Cormen, et al.: Introductions to Algorithms, Third Edition, MIT Press Tobias Werth: Dynamische Programmierung (Hallo Welt, 2004) Ludwig Höcker: Dynamische Programmierung (Hallo Welt, 2012) http://www.algorithmist.com/index.php/ Longest_Increasing_Subsequence http://en.wikipedia.org/wiki/longest_ increasing_subsequence Hallo Welt Wiki Julian Brost Dynamische Programmierung 11. Juni 2013 39 / 39