Christian Scheideler u. v. a. Paderborn, 1. Juli 2011 Abgabe 8. Juli 2011, 11.15 Uhr Kästen im D3-Flur Übungsblatt zur Vorlesung Datenstrukturen und Algorithmen SS 2011 Blatt 12 Aufgabe 1: Gegeben sei eine leere Hashtabelle mit m = 13. 1. Nachfolgend betrachten wir Hashing mit verketteten Listen zur Kollisionsverwaltung. Wählen Sie unter den unten aufgeführten Hashfunktionen eine gute aus und begründen Sie ihre Wahl. Stellen Sie weiterhin die Hashtabelle nach dem Einfügen der Schlüssel in der Reihenfolge 12, 23, 13, 56, 26, 45, 10 dar. h11 (x) := (3x + 1) mod m h12 (x) := ((2x + 7) mod 11) mod m 2. Nachfolgend betrachten wir Hashing mit offener Adressierung. Wählen Sie unter den unten aufgeführten Hashfunktionen eine gute aus und begründen Sie ihre Wahl. Stellen Sie weiterhin die Hashtabelle nach dem Einfügen der Schlüssel in der Reihenfolge 12, 23, 13, 56, 26, 45, 10 dar. h21 (x, i) := (2 · h12 (x) + 26 · i) mod m h22 (x, i) := (h11 (x) + i) mod m Aufgabe 2: Gegeben sei ein Array A = (a1 , a2 , . . . , an ) mit ai ∈ Z \ {0} für 1 ≤ i ≤ n. a) Entwickeln Sie einen Algorithmus, welcher mittels geeigneter Operationen das Array A in ein Array ASort = (a01 , a02 , . . . a0j , . . . , a0n ) überführt, wobei für ein festes j gilt: a0i < 0 für alle 1 ≤ i ≤ j und a0i > 0 für alle j < i ≤ n. Die Laufzeit Ihres Algorithmus sollte O(n) nicht überschreiten. Dabei darf der Algorithmus nur konstant viele zusätzliche Speicherplätze verwenden. Geben Sie den Pseudocode Ihres Algorithmus an. b) Zeigen Sie die Korrektheit und analysieren Sie das Laufzeitverhalten und den Speicherplatzbedarf Ihres Algorithmus. Aufgabe 3: Beweisen Sie, dass es keinen Algorithmus geben kann, der die folgenden drei Eigenschaften gleichzeitig erfüllt: • Der Algorithmus benutzt wie ein Vergleichssortierer nur Vergleiche, Zuweisungen, usw. • Der Algorithmus erstellt aus einem Array A einen binären Suchbaum mit den Elementen aus A. • Bei Eingabe eines Arrays A der Länge n hat der Algorithmus Laufzeit O(n). Aufgabe 4: Gegeben sei eine Menge von n Messbechern M1 , . . . , Mn . Der Messbecher Mi hat das Volumen Vi ∈ N Litern. Mit Hilfe der Messbecher sollen genau l ∈ N Liter Wasser in einen Tank geschüttet werden. Dabei kann ein Messbecher immer nur komplett gefüllt und in den Tank geschüttet werden. Aus dem Tank kann kein Wasser mehr entnommen werden. Ein Messbecher kann natürlich mehrfach verwendet werden. Einer der Messbecher hat ein Volumen von genau einem Liter. Gesucht ist ein Algorithmus, der liefert, wie viele Schüttvorgänge mindestens benötigt werden. Beispiel: Für n = 3, V1 = 1, V2 = 20, V3 = 50 und l = 61 werden mindestens 4 Schüttvorgänge benötigt (l = V2 + V2 + V2 + V1 ). 1. Geben Sie die Rekursionsgleichung für OP T (i, j) an. OP T (i, j) ist die minimale Anzahl der Schüttvorgänge, um j Liter in den Tank zu füllen, wenn nur die Messbecher M1 , . . . , Mi verwendet werden dürfen. 2. Geben Sie den Pseudocode eines Algorithmus an, der OP T (n, l) in Laufzeit O(n · l) berechnet. Aufgabe 5: Wir betrachten das Problem der Matrix-Ketten Multiplikation. Gegeben eine Sequenz hA1 , A2 , . . . , An i von n ∈ N Matrizen, ist es das Ziel möglichst effizient das Produkt C = A1 A2 · · · An berechnen. Das Problem hierbei ist nicht, die einzelnen MatrixMutliplikationen effizient durchzufürhren, sondern zu entscheiden, in welcher Reihenfolge wir die Matrixmultiplikationen durchzuführen, sodass die Anzahl der Skalarmultiplikationen minimiert wird. Da die Matrixmultiplikation assoziativ ist, stehen uns hierfür viele Möglichkeiten zur Verfügung. D. h. unabhängig davon, wie wir das Produkt A1 A2 · · · An klammern, bleibt das Ergebnis unverändert. Allerdings beeinflusst die Reihenfolge, in der wir das Produkt klammern, die Anzahl der Skalarmultiplikationen, die benötigt werden, um das Matrixprodukt zu berechnen. Zur Berechnung des Produktes zweier Matrizen verwenden wir im Folgenden der Einfachheit halber den O(n3 )-Algorithmus MatrixMultiplikation (Foliensatz 18, Folie 11). Dieser benötigt für die Berechnung des Produktes einer p × q und einer q × r Matrix p · q · r Skalarmultiplikationen. Beispiel: Sei A eine 10 × 30 Matrix, B eine 30 × 5 Matrix und C eine 5 × 60 Matrix. Dann benötigt die Berechnung des Produktes A · B · C mit der Klammerung A · (B · C) gerade (30 · 5 · 60) + (10 · 30 · 60) = 27000 Operationen, mit der Klammerung (A · B) · C allerdings nur (10 · 30 · 5) + (10 · 5 · 60) = 4500 Operationen. Im obigen Beispiel ist somit die Berechnung des Produktes A · B · C mit der Klammerung (A · B) · C effizienter als mit der Klammerung A · (B · C). Wie bestimmen wir nun aber eine Klammerung, die die Anzahl der durchzuführenden Skalarmultiplikationen (Kosten) bei der Berechnung des Produktes A1 A2 · · · An minimiert? Im Folgenden bezeichnen wir eine derartige Klammerung als optimale Klammerung. Formal lässt sich das Problem der Matrix-Ketten Multiplikation somit nun wie folgt definieren: Gegeben eine Sequenz hA1 , . . . , An i von n Matrizen, wobei die Matrix Ai , i ∈ {1, . . . , n}, die Dimension pi−1 × pi hat, berechne eine Klammerung für das Produkt A1 A2 · · · An , sodass die Anzahl der Skalarmultiplikationen minimiert wird. Eine Möglichkeit zur Berechnung einer optimalen Klammerung ist, alle möglichen Klammerung zu überprüfen, was allerdings Zeit O(2n ) benötigt und somit i. A. sehr langsam ist. Eine andere Möglichkeit zur Berechnung einer optimalen Klammerung ist, dieses Problem mit Hilfe des Prinzips der dynamischen Programmierung zu lösen. Im Folgenden wird schrittweise beschrieben, wie wir das Problem auf diese Weise lösen. Schritt 1: Struktur einer optimalen Klammerung Im Folgenden bezeichne Ai...j , i ≤ j das Produkt von Ai Ai+1 · · · Aj . Für i < j muss jede Klammerung von Ai Ai+1 · · · Aj , dieses Produkt zwischen Ak und Ak+1 , i ≤ k < j aufsplitten. D. h. für einen Wert k berechnen wir zunächst die Matrizen Ai...k und Ak+1...j und multiplizieren diese anschließend miteinander, um das Produkt Ai...j zu erhalten. Die Kosten dieser Klammerung sind somit gegeben durch die Kosten zur Berechnung von Ai...k , plus die Kosten der Berechnung von Ak+1...j , plus die Kosten, diese beiden Matrizen miteinander zu multiplizieren. Die optimale Teilstruktur dieses Problems ist somit wie folgt definiert: Angenommen eine optimale Klammerung von Ai Ai+1 · · · Aj teilt dieses Produkt zwischen Ak und Ak+1 auf. Dann lässt sich zeigen, dass die Klammerung der Teilkette Ai Ai+1 · · · Ak innerhalb der optimalen Klammerung von Ai Ai+1 · · · Aj ebenfalls optimal ist. Eine optimale Klammerung für eine Sequenz von Matrizen können wir somit berechnen, indem wir das Problem in zwei Teilprobleme aufteilen, für die wir eine optimale Klammerung finden und anschließend die optimalen Lösungen der Teilprobleme zu einer Gesamtlösung zusammensetzen. Schritt 2: Beschreibung einer rekursiven Lösung Im Folgenden bezeichne m[i, j], 1 ≤ i ≤ j ≤ n, die minimale Anzahl an Skalaroperationen, die benötigt wird, um Ai...j zu berechnen. m[1, n] enthält somit die minimalen Kosten, die für die Berechnung des Produktes A1...n benötigt werden. Rekursiv lässt sich m[i, j] wie folgt definieren: ( 0 if i = j m[i, j] = mini≤k<j {m[i, k] + m[k + 1, j] + pi−1 pk pj } if i < j Um zusätzlich abzuspeichern, wie wir eine optimale Lösung konstruieren können, definieren wir s[i, j], 1 ≤ i ≤ j ≤ n, als den Wert k, an dem wir das Produkt Ai Ai+1 . . . Aj aufteilen müssen, um eine optimale Klammerung zu erhalten. D. h. s[i, j] entspricht einem Wert k für den gilt: m[i, j] = m[i, k] + m[k + 1, j] + pi−1 pk pj . Schritt 3: Berechnung der optimalen Kosten Die Berechnung von m[i, j] für alle 1 ≤ i ≤ j ≤ n mittels der Rekursionsgleichung aus Schritt 2 würde einen exponentiellen Zeitaufwand benötigen, was somit nicht besser wäre als die brute-force Methode (alle Klammerungen durchzuprobieren). Eine wichtige Beobachtung, die wir hierbei allerdings machen können, ist dass wir relativ wenig Teilprobleme haben: Ein Problem für jede Wahl von i und j mit 1 ≤ i ≤ j ≤ n, bzw. n insgesamt 2 + n = Θ(n2 ) Teilprobleme insgesamt. Anstatt die Lösungen für diese Teilprobleme, wie bei dem rekursiven Ansatz, immer wieder neu zu berechnen, speichern wir diese in einer Tabelle ab und greifen lediglich drauf zu, wenn wir sie benötigen. Algorithmus 1 beschreibt eine Implementierung dieses Ansatzes. Algorithm 1 Matrix-Chain-Order(p0 , . . . , pn ) 1: for i ← 1 to n do 2: m[i, i] ← 0 3: end for 4: for l ← 2 to n do l bezeichnet die Kettenlänge 5: for i ← 1 to n − l + 1 do 6: j ←i+l−1 7: m[i, j] ← ∞ 8: for k ← i to j − 1 do 9: q ← m[i, k] + m[k + 1, j] + pi−1 pk pj 10: if q < m[i, j] then 11: m[i, j] ← q 12: s[i, j] ← k 13: end if 14: end for 15: end for 16: end for 17: return m and s Ihre Aufgabe: Schreiben Sie ein Java-Programm, das folgende Aufgaben erfüllt: 1. Das Programm erwartet als Parameter den Dateinamen einer Textdatei, welche eine Folge von n + 1 natürlichen Zahlen p0 p1 . . . pn enthält und ließt diese ein. 2. Berechnen Sie mittels des oben angegebenen Algorithmus’ Matrix-Chain-Order die Werte m[i, j] für alle 1 ≤ i ≤ j ≤ n. Ihr Programm soll dabei nach den unten definierten Ausgabespezifikationen die Werte m[i, j] für alle 1 ≤ i ≤ j ≤ n ausgeben. Bitt beachten Sie zusätzlich folgende Punkte: • Halten Sie die unten angegebenen Ein- und Ausgabespezifikationen bitte genau ein. Das bedeutet insbesondere: – Die Eingabedatei (bzw. der Pfad zu dieser inkl. des Dateinamens) soll als Eingabeparameter an das Programm übergeben werden. D. h. beispielsweise, dass die Eingabedatei nicht immer inputfile.txt heißen muss, die Eingabedatei nicht im gleichen Verzeichnis wie das Programm liegen muss und dass die Eingabe eines Dateinamens nicht über eine Eingabeaufforderung geschehen soll. – Es darf nur (und muss) genau das ausgegeben werden, was in der Ausgabespezifikation weiter unten definiert ist. D. h. es dürfen keine zusätzlichen Ausgaben erfolgen und jede Zeile beginnt mit i : “ , wobei i die aktuelle Zeilennummer ” bezeichnet. • Ihr Programm muss in Java (Version ≤ 1.6) geschrieben sein. • Ihr Programm darf nur aus einer einzigen (Java-)Quelldatei bestehen. • Die Quelldatei Ihres Programmes muss Blatt12.java heißen. Dabei ist auf Groß- und Kleinschreibung zu achten. D. h. blatt12.java ist beispielsweise nicht korrekt. • Ihr Programm muss mit dem Befehl javac Blatt12.java auf einem Linux-Rechner der Uni kompilierbar und mit dem Befehl java Blatt12 ausführbar sein. • Die Abgabe dieser Aufgabe geschieht in digitaler Form. Hierfür laden Sie Ihr Programm bitte auf der Seite http://funalg.cs.upb.de bis zum 8. Juli 2011, 11.15 Uhr hoch. • Sie dürfen Ihr Programm in Gruppen von bis zu vier Personen abgeben. Geben mehrere Gruppen das selbe Programm ab, so erhalten die betroffenen Gruppen für diese Aufgabe 0 Punkte. • Beispielinstanzen zum Testen Ihres Programmes inkl. korrekter Ausgaben finden Sie unter http://funalg.cs.upb.de/dua/blatt12.zip. Bei Missachtung dieser Punkte wird Ihr Programm nicht ausgewertet! Eingabespezifikation: Die einzulesende Datei besteht aus genau einer Zeile, welche eine Folge p0 p1 . . . pn von n + 1 natürlichen Zahlen enthält. Die Zahlen sind jeweils durch ein Leerzeichen voneinander getrennt. Die Zeile endet mit einem abschließenden Leerzeichen. Die pi definieren dabei die Dimensionen von n Matrizen A1 , . . . , An . Die Matrizen selbst werden nicht benötigt und sind daher auch nicht gegeben. Die Matrix Ai habe die Dimension pi−1 × pi , 1 ≤ i ≤ n. Ausgabespezifikation: Ihr Programm soll n+1 Zeilen ausgeben (wobei n+1 die Anzahl der eingelesenen Zahlen bezeichnet). Die i-te Zeile, 1 ≤ i ≤ n, beginne dabei mit i : und enthalte durch Leerzeichen voneinander getrennt die Werte, die in m[i, i], m[i, i + 1], . . . , m[i, n] gespeichert sind. Die (n + 1)-te Zeile enthalte die Anzahl der Skalarmultiplikationen, die bei der berechneten Klammerung zur Berechnung des Matrixproduktes von Matrizen mit den angegebenen Dimensionen benötigt werden. D. h. die Ausgabe Ihres Programmes soll wie folgt aussehen: 1 : m[1,1] 2 : m[2,2] 3 : m[3,3] . . . i : m[i,i] . . . n : m[n,n] Kosten m[1,2] m[1,3] ... m[1,n] m[2,3] m[2,4] ... m[2,n] m[3,4] m[3,5] ... m[3,n] m[i,i+1] m[i,i+2] ... m[i,n] Beispiel: Angenommen die einzulesende Textdatei mit dem Namen inputfile.txt enthält folgenden Inhalt: 6 2 8 9 10 4 5 2 4 Nach dem Kompilieren mit dem Befehel javac Blatt12.java soll Ihr Programm durch Aufruf von java Blatt12 inputfile.txt folgende Ausgabe liefern: 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 524 0 0 0 0 0 0 0 0 96 252 444 452 504 484 524 144 324 404 444 460 476 720 648 808 444 508 360 540 300 372 200 120 200 40 72 40