Prof. Dr. Dennis Hofheinz Lukas Barth, Lisa Kohl K a r l s ru h e r I n s t i t u t f ü r T e c h n o l o g i e Institut für Theoretische Informatik 12. Übungsblatt zu Algorithmen I im SoSe 2016 https://crypto.iti.kit.edu/index.php?id=algo-sose16 {lisa.kohl,lukas.barth}@kit.edu Musterlösungen Aufgabe 1 (Rucksackproblem, 2 + 2 Punkte) Gegeben seien n = 5 Gegenstände mit Gewichten w1 = 40, w2 = 18, w3 = 20, w4 = 15, w5 = 10 und jeweiligen Profiten p1 = 90, p2 = 45, p3 = 52, p4 = 42, p5 = 35. Gesucht ist eine Teilmenge der Gegenstände, die insgesamt das Gewicht M = 50 nicht überschreitet und die Summe der zugehörigen Profite maximiert. a) Finden Sie diese Teilmenge mit Hilfe dynamischer Programmierung. Verwenden Sie dazu wie in der Vorlesung vorgestellt eine Tabelle (Vorlesung vom 06.07., Folie 13). b) Finden Sie diese Teilmenge mit Hilfe von Branch-and-Bound. Verwenden Sie dazu die Schreibweise aus der Vorlesung (Vorlesung vom 11.07., Folie 12). Sortieren Sie die Gegenstände zunächst gemäß der Annahme im vorgestellten Algorithmus. Benutzen Sie als obere Schranke den maximalen Wert, der erreichbar wäre, wenn auch Bruchteile von Gegenständen eingepackt werden dürften. Musterlösung: • Wir betrachten nur die Gewichte, die als Kombination auftreten können. i\C 0 1 2 3 4 5 10 0 0, (0) 0, (0) 0, (0) 0, (0) 35, (1) 15 0 0, (0) 0, (0) 0, (0) 42, (1) 42, (0) 18 0 0, (0) 45, (1) 45, (0) 45, (0) 45, (0) 20 0 0, (0) 45, (1) 52, (1) 52, (0) 52, (0) 25 0 0, (0) 45, (1) 52, (1) 52, (0) 77, (1) i\C 0 1 2 3 4 5 35 0 0, (0) 45, (1) 52, (1) 94, (1) 94, (0) 38 0 0, (0) 45, (1) 97, (1) 97, (0) 97, (0) 40 0 90, (1) 90, (0) 97, (1) 97, (0) 97, (0) 43 0 90, (1) 90, (0) 97, (1) 97, (0) 97, (0) 45 0 90, (1) 90, (0) 97, (1) 97, (0) 129, (1) 28 0 0, (0) 45, (1) 52, (1) 52, (0) 80, (1) 48 0 90, (1) 90, (0) 97, (1) 97, (0) 132, (1) 30 0 0, (0) 45, (1) 52, (1) 52, (0) 87, (1) 33 0 0, (0) 45, (1) 52, (1) 87, (1) 87, (0) 50 0 90, (1) 90, (0) 97, (1) 97, (0) 132, (1) Damit liefert die Teilmenge {2, 3, 5} einen maximalen Profit von 132. • Es gilt wp11 = 2, 25, wp22 = 2, 5, wp33 = 2, 6, wp44 = 2, 8 und wp55 = 3, 5, wir müssen die Objekte also gerade in umgekehrter Reihenfolge betrachten. Der theoretisch maximal erreichbare Profit beträgt 5 p5 + p4 + p3 + 18 p2 = 141, 5. Wir schreiben C, wenn der Abzweig das Maximalgewicht überschreiten würde und B, wenn der Abzweig maximal ein Gewicht liefern würde, das nicht größer als das bisher entdeckte Optimalgewicht ist. 1 ????? (141,5) 1???? (141,5) B 11??? (141,5) 111?? (141,5) C Aufgabe 2 110?? (137,75) 1110? (140,25) C 10??? (136,5) 11100 (129) 1101? (137,75) C B 101?? (136,5) 1100? (133,25) C B 1011? 136,5 C B B 10110 (132) (Doktor Meta und dynamische Programmierung, 2 + 2 + 4 + 2 Punkte und 2 Bonuspunkte) Dem ebenso beharrlichen wie größenwahnsinnigen Doktor Meta ist es endlich gelungen, er steht kurz vor Übernahme der Weltherrschaft. Dies möchte er gebührend feiern und beauftragt dazu seinem ebenso getreuen wie tollpatschigen Gehilfen Røbøt mit der Zusammenstellung einer Playlist. Damit diese seinen Ansprüchen genügt und um schlechte Übergänge zu vermeiden, hat Doktor Meta eine Liste von n Liedern zusammengestellt und darüber hinaus zu je zwei Liedern i und j eine Bewertung ci,j abgegeben, die anzeigt, wie gut Lied j klingt, wenn es direkt nach Lied i abgespielt wird. Die Aufgabe von Røbøt ist es nun eine Reihenfolge zu finden (wobei jedes Lied genau einmal gespielt werden soll), so dass die Summe der Bewertungen ci,j für hintereinander gespielte Lieder maximiert wird. Helfen Sie Røbøt, indem Sie berechnen, wie hoch diese Summe maximal sein kann. Zwei Bonuspunkte gibt es, wenn Sie zusätzlich die zugehörige Reihenfolge der Lieder zurückgeben. Der Algorithmus soll eine Laufzeit in 1 O n2 2n haben. a) Zählen Sie die Tabelle(n) auf, die Ihr Algorithmus verwenden wird und erklären Sie die Bedeutung der jeweiligen Einträge. b) Geben Sie die Rekurrenz und den Basis-Fall an, den Ihr Algorithmus verwendet. c) Geben Sie Ihren Algorithmus im Pseudo-Code an. d) Analysieren Sie die Laufzeit Ihres Algorithmus. Begründen Sie ihre Antwort kurz. Musterlösung: a) Die zu Grunde liegende Tabelle besteht aus Einträgen der Form C[S, i], wobei S eine Teilmenge der n Lieder ist und i ein Element aus S. C[S, i] entspricht der maximal möglichen Kompatibilität für eine Liste der Lieder in Teilmenge S, die mit Lied i endet. ( b) C[S, i] = 0 maxk∈S\{i} (C[S\{i}, k] + ck,i ) falls |S| = 1 falls|S| > 1 c) Ein möglicher Algorithmus basierend auf dieser Rekursion ist der Folgende: Function songordering(n : N, c : Matrix of R): nat C : Matrix of R for j:= 1 to n do C[{j}, j]:= 0 for j:= 2 to n do foreach S with |S| = j do foreach i ∈ S do 2 C[S, i]:= maxk∈S\{i} (C[S\{i}, k] + ck,i ) return max1≤i≤n C[{1, . . . , n}, i] d) Die erste Schleife benötigt n Durchläufe. Das Finden des Maximums in der zweiten Schleife benötigt Zeit O(|S|). Daher läuft die innerste Schleife „foreach i ∈ S“ insgesamt in Zeit O |S|2 , beziehungsweise O(ns ) für beliebiges S. Die äußeren beiden Schleifen iterieren über alle möglichen Teilmengen von {1, ..., n} der Größe mindestens 2, diese Anzahl kann nach oben mit 2n abgeschätzt werden. Damit ergibt sich insgesamt eine Laufzeit von O 2n n2 . Bonus) Um die Liste der Lieder auszugeben, speichert man in C[S, i] zusätzlich zum optimalen Wert auch das Element k, für das dieser Wert erreicht wurde, also (für |S| > 1): C[S, i] = ( max (C[S\{i}, k] + ck,i ), arg max(C[S\{i}, k] + ck,i )) k∈S\{i} k∈S\{i} Algorithmisch lässt sich dies leicht in die vorletzte Zeile des Pseudocodes integrieren. Will man nun die optimale Liste der Lieder abrufen, so schaut man zunächst ins Feld C[{1, . . . , n}, n] und sieht dort nicht nur der optimal erreichbaren Wert, sondern auch das Lied, das in einer optimales Lösung vor Lied n kommt. Sei dieses Lied k. Als nächstes schaut man in das Feld C[{1, . . . , n − 1}, k] und findet dort das Lied, das in einer optimalen Lösung vor k kommt. Dies setzt man fort, bis man die komplette Liste der Lieder (in umgekehrter Reihenfolge!) rekonstruiert hat. Aufgabe 3 (Kickern, 1 + 2 + 4 Punkte und 2 + 2 Bonuspunkte) In der Informatik-Fakultät wird begeistert gekickert, und so soll eine Fakultätsmeisterschaft ausgetragen werden. Da Informatiker von Natur aus kompetitiv sind, soll nachher möglichst für jedes Paar von Mitarbeitern der Fakultät feststehen, wer gegen wen gewonnen hat. Gleichzeitig haben die n Mitarbeiter (hierzu zählen auch die Professoren) aber mit dem Betreuen von Vorlesungen so viel zu tun, dass nicht daran zu denken ist, n2 Spiele auszutragen. Daher spielt immer nur jeder Mitarbeiter gegen seinen unmittelbaren Vorgesetzten und umgekehrt, also z.B. jeder Professor gegen alle seine Mitarbeiter, usw. Jeder Mitarbeiter außer dem Dekan hat genau einen Vorgesetzten. Für jedes ausgeführte Spiel werden die Beteiligten sowie die erreichte Tordifferenz notiert. So bedeutet z.B. der Eintrag (Bob, Alice, 5) dass Bob gegen Alice mit 5 Toren Differenz gewonnen hat, während (Eve, M allory, −3) bedeutet, dass Eve gegen Mallory mit 3 Toren Differenz verloren hat. Um auch für Informatiker, die nicht gegeneinander gespielt haben, festzustellen, wer gegen wen gewonnen hat, wird nach folgenden zwei Regeln vorgegangen: • Hat Spieler A gegenüber Spieler B eine Tordifferenz von x, Spieler B gegenüber Spieler C eine Tordifferenz von y, und haben A and C nicht gegeneinander gespielt, so hat A gegenüber C die Tordifferenz x + y • Hat Spieler A gegenüber Spieler B eine positive Tordifferenz (d.h. größer als 0), so hat A gegen B gewonnen. Beispiel: Folgende Spiele haben stattgefunden: (Bob, Alice, 5), (Eve, M allory, −3), (Alice, Eve, 1) Damit hat Bob gegenüber Mallory eine Tordifferenz von 3 und somit gegen Mallory gewonnen. Im Folgenden dürfen Sie davon ausgehen, dass alle Namen maximal 100 Zeichen lang sind, und die maximale Tordifferenz einer Partie 10 beträgt. 3 a) Warmup: Folgende Spiele haben stattgefunden: (Cormen, Garey, -6), (Garey, Johnson, -3), (Garey, Rivest, 1), (Floyd, Cormen, -4), (Floyd, Warshall, 2), (Cormen, Dijkstra, -2), (Bellman, Dijkstra, 4), (Bellman, Kruskal, 1), (Bellman, Jarnik, -2), (Bellman, Prim, 1), (Ford, Dijkstra, -2), (Ford, Karatsuba, 4), (Ford, Offman, 3), (Offman, Euler, -4), (Offman, Steiner, -3) Nach der oben angegebenen Definition von gewonnen: Gegen wen hat Herr Garey gewonnen? Gegen wen steht es unentschieden? b) Begründen Sie kurz: Die Definition der Tordifferenz ist konsistent, insbesondere kann folgender Fall nicht eintreten: – A hat gegenüber B Tordifferenz x – A hat gegenüber C Tordifferenz y – B hat gegen D gespielt und Tordifferenz x0 gegenüber D – C hat gegen D gespielt und Tordifferenz y 0 gegenüber D – x + x0 6= y + y 0 c) Skizzieren Sie einen Algorithmus, der für eine gegebene Person A und eine Ergebnisliste in O(n log n) Zeit berechnet, gegen wen A gewonnen hat. Ihr Algorithmus hat dabei ausschließlich der Ergebnisliste als Eingabe zur Verfügung, und keine Zusatzinformationen wie z.B. die Liste der Mitarbeiter oder die Hierarchie innerhalb der Informatik-Fakultät. Geben Sie an welche Datenstrukturen Sie jeweils verwenden und beachten Sie die dafür anfallenden Laufzeiten. d) Bonusaufgabe für 2 Bonuspunkte: Begründen Sie das Laufzeitverhalten Ihres Algorithmus. e) Bonusaufgabe für 2 Bonuspunkte: Lässt sich das auch in erwartet O(n) berechnen? Oder sogar deterministisch in O(n)? Wenn ja, skizzieren Sie jeweils, wie das geht. Wenn nein, begründen Sie kurz. Musterlösung: a) Garey hat gewonnen gegen: Rivest, Cormen, Floyd, Warshall, Dijkstra, Kruskal, Prim, Ford, Karatsuba, Offman, Euler und Steiner. b) Aus der Aufgabenstellung, insbesondere aus der Bedingung, dass jeder nur gegen seinen direkten Vorgesetzten spielt, ergibt sich, dass der Graph, in dem jede Person ein Knoten ist, und in dem eine Kante existiert, wenn zwei Personen gegeneinander gespielt haben, ein Baum ist. In einem Baum existiert zwischen zwei Knoten genau ein eindeutiger Pfad. Sei P = hA, v1 , v2 , . . . vk , Di der Pfad von A nach D. Es ist klar, dass nur entweder vk = B, vk = C oder vk 6∈ {B, C} gelten kann. O.b.d.A. nehmen wir an, dass vk 6= B. Da B ein Nachbar von D ist, muss der Pfad P 0 von A nach B über D führen, und außerdem der einzige Pfad von A nach B sein. Nach Definition (und mit den Symbolen aus der Aufgabenstellung) ist damit aber die Tordifferenz von A gegenüber B (also x) festgelegt als die Tordifferenz von A gegenüber D plus die Tordifferenz von D gegenüber B. Sei die Tordifferenz von A gegenüber D z = y + y 0 , dann gilt also x = z − x0 = y + y 0 − x0 und damit x + x0 = y + y 0 . c) Die Grundidee ist, dass wir den in Aufgabenteil b beschriebenen Graphen aufbauen. Es fällt auf, dass die Tupel (Person1, Person2, Ergebnis) gerade genau zwei gerichtete Kanten (mit dazugehörigem Gewicht) spezifizieren: Die Kante (P erson1, P erson2, Ergebnis) sowie die Kante (P erson2, P erson1, −1 · Ergebnis). Damit können wir uns einfach eine Liste aller gerichteten Kanten erstellen. Wir sortieren zunächst die Liste dieser Kanten nach dem ersten der beiden Namen und wenden dann die aus der Vorlesung bekannte Technik (Foliensatz vom 13.6., Folie 18) an, um daraus eine Adjazenzfelddarstellung des Graphen zu erhalten. Diese Darstellung erweitern wir um ein Feld T [v] pro Knoten v, das mit 0 initialisiert wird, und das die Tordifferenz von A gegenüber diesem Knoten speichern wird. Auf dem erstellten Graphen starten wir dann eine 4 Tiefensuche (oder Breitensuche) ausgehend von A. Sei c[(u, v)] das Kantengewicht der gerichteten Kante (u, v). Die Tiefen- (oder Breiten-)suche wird dann beim Relaxieren jeder Kante (u, v) T [v] = T [u] + c[(u, v)] setzen. Abschließend geben wir alle die Knoten v aus, für die T [v] > 0 gilt. d) Um die Kanten- aus der Ergebnisliste zu erstellen, muss jedes Ergebnis einmal angeschaut und zwei Einträge in eine verkettete Liste eingefügt werden. Dies benötigt insgesamt O(n) Zeit. Das Sortieren der Kantenliste läuft in O(n log n) (oder, mit etwas Argumentation bezüglich endlicher Alphabete und Namenslängen sogar in O(n). . . ). Die Tiefensuche benötigt erneut O(n) Zeit, und abschließend müssen nur nocheinmal n Einträge in T angeschaut werden. Insgesamt ergibt sich O(n log n) Zeitkomplexität. e) Erwartet O(n): Sei h eine zufällig aus einer universellen Familie gezogene Hashfunktion. Wir initialisieren zwei Hashtabellen mit je n Einträgen und einen Stack. Die erste Hashtabelle sei die Punkte-Hashtabelle. In diese wird anfangs (A, 0) eingefügt. Die zweite Hashtabelle sei die Ergebnis-Hashtabelle. In diese werden für jedes Ergebnis der Form (Person1, Person2, Ergebnis) die Einträge (P erson1, (P erson2, Ergebnis)) und (P erson2, (P erson1, −1 · Ergebnis)) eingefügt. Außerdem wird A auf den Stack gelegt. Danach geht man wie folgt vor: Entnehme das oberste Element des Stacks (X) und ermittle seine Nachbarn, indem in der Ergebnis-Hashtabelle nach dem Element gesucht wird. Für jeden Nachbarn (Y ) wird geschaut, ob schon Punkte für ihn in der Punkte-Tabelle eingetragen sind. Wenn nicht, werden Punkte eingetragen, und zwar bekommt Y die Punke von X plus des Ergebnisses zwischen X und Y . Danach wird Y auf den Stack gelegt. Wiederhole, bis der Stack leer ist. Alle Zugriffe auf die Hashtabellen (sowie auf den Stack) liegen dank universellem Hashing erwartet in O(1). Im die Gesamtlaufzeit von erwartet O(n) zu sehen, genügt es zu zeigen, dass alle Elemente nur konstant oft betrachtet werden müssen. Zunächst ist klar, dass jedes Element nur einmal in den Stack eingefügt (und damit auch nur einmal herausgenommen) wird: Wird ein Element auf den Stack gelegt, so werden auch Punkte für das Element eingetragen, und ein Element, für das bereits Punkte eingetragen wurden, wird nie wieder auf den Stack gelegt. Etwas schwieriger ist es, zu sehen, dass die Anzahl der „Nachbarn“, die betrachtet werden müssen, insgesamt auch nur 2(n − 1) ist: Jede „Schaue für einen Nachbarn, ob Punkte eingetragen sind“-Operation entspricht der Traversierung einer Kante im Graphen von Aufgabenteil b. Von diesen Kanten gibt es n − 1 Stück, und jede Kante kann nur dann von a nach b traversiert werden, wenn a der gerade vom Stack genommene Knoten ist. Da das pro Knoten nur ein einziges Mal passiert, wird diese Operation nur insgesamt 2(n − 1) Mal ausgeführt. Deterministisch O(n): In der Aufgabenstellung steht, dass alle Namen maximal 100 Zeichen lang sind und die maximale Tordifferenz 10 beträgt. Da außerdem das Alphabet endlich ist, können wir also auf die Ergebnis-Tupel auch ganzzahlige Sortierung (z.B. Bucketsort) anwenden. Damit läuft der in Aufgabenteil c skizzierte Algorithmus in O(n). 5