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 9. Ü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 (Dijkstra, 4 Punkte) Wir beschäftigen uns mit dem Algorithmus von Dijkstra aus der Vorlesung. Sei ein Graph G = (V, E) wie unten dargestellt. Führen Sie Dijkstras Algorithmus auf G aus. Sie können die Darstellung von G benutzen, um die Ergebnisse nach der Ausführung darin einzutragen. Schreiben Sie dazu in jeden Knoten v ∈ V rechts vom Doppelpunkt die Länge des kürzesten Pfads vom Startknoten s aus gesehen und links vom Doppelpunkt den Vorgänger auf diesem kürzesten Pfad. Zeichnen Sie den Baum der kürzesten Wege von s zu allen anderen Knoten aus V in G ein. Geben Sie die Reihenfolge der Knoten an, in der sie als scanned markiert werden. Geben Sie außerdem für jeden Knoten v ∈ V alle Zwischenwerte d[v] und parent[v] an, die während Ausführung des Algorithmus angenommen werden. Darstellung von G: a 10 s : : 3 1 5 5 1 1 3 c 7 b 3 d : : : 2 6 6e 1 2 f e : 1 : Musterlösung: Die Knoten werden in der Reihenfolge s, d, c, e, f, b, a als scanned markiert. Weiter gilt für die Werte von d und parent: • d[s] = 0 und parent[s] = s während der gesamten Ausführung • d[a] = ∞ und parent[s] = ⊥ nach Initialisierung, d[a] = 10 und parent[s] = s nach Relaxieren der von s ausgehenden Kanten, d[a] = 9 und parent[s] = c nach Relaxieren der von c ausgehenden Kanten und schließlich d[a] = 8 und parent[s] = b nach Relaxieren der von b ausgehenden Kanten • d[b] = ∞, 11, 7 und parent[b] = ⊥, c, e 1 • d[c] = ∞, 5, 4 und parent[b] = ⊥, s, d • d[d] = ∞, 1 und parent[b] = ⊥, s • d[e] = ∞, 7, 5 und parent[b] = ⊥, d, c • d[f ] = ∞, 7, 6 und parent[b] = ⊥, d, e a 10 s b:8 3 1 s:0 5 5 1 3 1 c 7 3 b d d:4 e:7 s:1 2 6 6e 1 2 f e c:5 Aufgabe 2 1 e:6 (Unzuverlässige Kommunikation, 4 Punkte) Sie sind Administrator eines Computernetzwerks bestehend aus n Rechnern und Netzwerkverbindungen zwischen diesen. Leider sind die Netzwerkverbindungen nicht absolut zuverlässig: Jede Verbindung zwischen zwei Rechnern a und b hat eine Wahrscheinlichkeit 0 < p(a,b) < 1, dass ein auf dieser Verbindung gesendetes Paket nicht ankommt. Die Verbindungen sind gerichtet, das heißt es kann sein, dass p(a,b) 6= p(b,a) gilt. Ihre Aufgabe ist es nun, zwischen zwei gegebenen Rechnern einen möglichst zuverlässigen Pfad zu finden. Geben Sie hierfür einen möglichst effizienten Algorithmus an, und begründen Sie kurz, warum dieser funktioniert. Musterlösung: Das Problem wird natürlich als Graph modelliert: Hier stehen Knoten für Rechner und Kanten für Verbindungen zwischen Rechnern. Die Kantengewichte c : E → (0, 1) symbolisieren dabei die Wahrscheinlichkeit, dass ein Paket auf der entsprechenden Kante erfolgreich versendet wird, also c((a, b)) = 1 − p(a,b) . Sei nun ein Startrechner S und ein Zielrechner T gegeben. Wir führen einen modifizierten Algorithmus von Dijkstra aus: Die vorläufigen Distanzen d[v] stehen nun für die Zuverlässigkeit des vorläufig besten gefundenen Pfades von S nach v und werden mit 0 (statt mit ∞) initialisiert (Ausnahme: d[S] = 1). Das parent-Feld funktioniert wie in Dijkstras Algorithmus und speichert den Vorgänger im vorläufig besten Pfad. Eine Kante (u, v) wird relaxiert, indem die vorläufige Zuverlässigkeit von S nach u mit c((u, v)) multipliziert wird, und die vorläufige Zuverlässigkeit von S nach v (zusammen mit dem parent-Zeiger) aktualisiert wird, falls ein besserer Pfad gefunden wurde, also: d[v] = max{d[v], d[u] · c((u, v))} 2 Die Prioritätswarteschlange ist in unserem Fall ein Max-Heap, der die vorläufige Zuverlässigkeit d[v] als Schlüssel verwendet. Der Korrektheitsbeweis funktioniert analog zu Dijkstras Algoritmus: Zunächst ist es wichtig zu sehen, dass in dem Moment, in dem ein Knoten v aus der Prioritätswarteschlange entfernt wird, kein Pfad mit höherer Zuverlässigkeit als d[v] von S nach v mehr existieren kann. Wir beweisen dies mittels Induktion über die Länge des optmialen S − v-Pfades: Für Länge 1 ist die Behauptung trivialerweise wahr: Der Pfad besteht nur aus S, d[S] = 1, und besser wird es nicht. Gelte nun also die Behauptung für eine beliebige, aber feste Pfadlänge l. Sei der Pfad P = hS, . . . , u, vi ein optimaler S − v-Pfad der Länge l + 1. Wenn zu dem Zeitpunkt, zu dem v aus dem Heap genommen wird, parent[v] = u gilt, dann ist d[v] per Definition optimal. Angenommen also, dass parent[v] = w 6= u gilt, und dass d[v] = d[w] · c((w, v)) < d[u] · c((u, v)). Wir wissen, dass die Länge des optimalen S − u-Pfades P 0 = hS, . . . ui hier l ist (da P eine Länge von l + 1 hat). Nach Induktionsvorraussetzung wurde also d[u] korrekt berechnet, und insbesondere muss d[u] > d[v] gelten. Damit wurde aber u bereits vor v aus dem Max-Heap entfernt, die Kante (u, v) wurde relaxiert und d[v] wurde korrekterweise auf d[u] · c((u, v)) gesetzt. Somit kann d[v] > d[u] · c((u, v)) nicht mehr gelten. Mit dieser Einsicht ist es einfach, die Korrektheit des gesamten Algorithmus zu sehen: Für jeden Knoten v gilt, dass falls es überhaupt einen S − v-Pfad gibt, v irgendwann in den Max-Heap eingefügt, und somit v auch irgendwann aus dem Max-Heap entfernt wird. Damit wird der optimale Pfad zu jedem von S erreichbaren Knoten errechnet. Für alle nicht erreichbaren Knoten bleibt d[v] = 0 gesetzt, was die Nichterreichbarkeit korrekt abbildet. Das Finden eines optimalen S − T -Pfades beschränkt sich danach darauf, die parent-Einträge korrekt von T zu S zurückzuverfolgen. Aufgabe 3 (Verbesserter Bellman-Ford-Algorithmus, 1 + 4 Punkte) Gegeben sei folgende Verbesserung des Bellman-Ford-Algorithmus: Im gerichteten Graphen G = (V, E) weisen wir zunächst den Knoten eine beliebige aber feste Ordnung zu. Die Knoten seien nun V = hv1 , v2 , v3 , . . . vn i. Nun können wir E in zwei Teile Ef ∪ Eb = E aufteilen, sodass Ef := {(vi , vj ) ∈ E | i < j} und Eb := {(vi , vj ) ∈ E | i > j} gilt. Intuitiv enthält Ef also alle Kanten, die von einem kleineren auf einen größeren Knoten zeigen (Eb entsprechend umgekehrt). Weiterhin sei folgende Änderung des Bellman-Ford-Algorithmus gegeben: Anstatt in jedem der n Durchläufe alle Kanten in irgendeiner Reihenfolge zu relaxieren, gehen wir in jedem Durchlauf wie folgt vor: Wir betrachten die Knoten in der Reihenfolge v1 , v2 , v3 , . . . vn und relaxieren dabei für jeden Knoten alle Kanten in Ef , die den Knoten verlassen. Danach betrachten wir alle Knoten in der Reihenfolge vn , vn−1 , . . . v1 und relaxieren dabei für jeden Knoten alle Kanten in Eb , die den Knoten verlassen. a) Zeigen Sie: Gf = (V, Ef ) und Gb = (V, Eb ) sind jeweils DAGs. b) Zeigen Sie: Die veränderte Version des Bellman-Ford-Algorithmus berechnet bereits nach dn/2e Schritten das Ergebnis, d.h. einen kürzesten s − t-Pfad oder (mindestens) einen negativen Kreis. Tipp: Überlegen Sie sich, was der Algorithmus auf den folgenden zwei Graphen tut. Diese beiden Graphen sind nur als Hilfestellung angegeben. Zur Lösung dieser Aufgabe ist es nicht erforderlich, dass Sie den Algorithmus auf ihnen ausführen. Es hilft Ihnen aber hoffentlich. (a) Ein einfacher Pfadgraph: V = {1, 2, . . . n}, E = {(i, i + 1) | i ∈ {1, 2 . . . n − 1}} (b) Zwei aufeinander zulaufende Pfadgraphen: V = {1, 2, . . . n} E = {(i, i + 1) | i ∈ {1, 2 . . . d n n − 1e}} ∪ {(i + 1, i) | i ∈ {d − 1e . . . n − 1, n}} 2 2 3 Musterlösung: Zunächst sei angemerkt, dass diese Variante von Bellman-Ford bekannt ist als Yen’s Improvement on Bellman-Ford. a) Angenommen, es gäbe einen Kreis in Gf . Wir laufen (n + 1) Schritte in diesem Kreis und schreiben die Indizes der hierbei gesehenen Knoten als Folge auf. Da alle Kanten in Gf von einem kleineren auf einen größeren Index zeigen, muss diese Folge streng monoton steigend sein. In Gf gibt es aber nur n Knoten, und somit nur n verschiedene Knotenindizes. Dies ist ein Widerspruch. Der Beweis für Gb funktioniert analog. b) Sei P = hS, . . . T i ein optimaler S − T -Pfad. Jede Kante in P liegt entweder in Ef oder in Eb . Die Idee ist nun, P in (maximale) Teilpfade zu zerlegen, die jeweils vollständig in Gf bzw. Gb liegen: O.b.d.A. gehen wir davon aus, dass die erste Kante von P in Ef und die letzte Kante von P in → ← → ← → ← Eb liegt. Dann lässt sich P schreiben als eine Folge von Teilpfaden hP1 , P1 , P2 , P2 , . . . , Pk , Pk i, → ← wobei alle Pi in Gf , und alle Pi in Gb liegen. Es ist einfach zu sehen, dass k ≤ → ← (Pi , Pi ) Paar enthalten. n 2 gelten muss: Ein enthält immer mindestens zwei Kanten, und P kann insgesamt maximal n Kanten Wir zeigen nun, dass i Iterationen des veränderten Bellman-Ford-Algorithmus ausreichend sind, → ← um den Teilpfad von P bis hS, . . . Pi , Pi i korrekt zu finden. Damit ist klar, dass nach k Iterationen P korrekt gefunden wurde. Dies zeigen wir induktiv: Für i = 0 ist die Behauptung trivial wahr: → ← Der Teilpfad von P mit 0 (Pi , Pi )-Paaren besteht nur aus S. Sei nun also i beliebig aber fest, → ← und der Pfad P 0 = hS, . . . Pi , Pi i somit korrekt gefunden. In der ersten Hälfte von Iteration i + 1 betrachten wir nun die Knoten in Reihenfolge aufsteigender Indizes und relaxieren ihre → ausgehenden Kanten in Ef . Damit werden alle Kanten in Pi+1 in der Reihenfolge, in der sie in → → Pi+1 auftreten, relaxiert, und somit wird P 0 um Pi+1 erweitert. In der zweiten Hälfte betrachten wir alles Knoten in umgekehrter Indexreihenfolge und relaxieren die eingehenden Kanten in Eb . ← ← Damit werden nun alle Kanten in Pi+1 in der korrekter Reihenfolge relaxiert, und P 0 um Pi+1 → ← erweitert. Am Ende von Iteration i + 1 wurde der Pfad P 00 = hS, . . . Pi+1 , Pi+1 i korrekt gefunden. Die Erkennung von negativen Kreisen funktioniert analog zum unveränderten Bellman-FordAlgorithmus: Sollte sich nach k Schritten noch eine vorläufige Distanz verändern, so liegt ein negativer Kreis vor. Aufgabe 4 (Doktor Meta, 6 Punkte) Als der ebenso orientierungsschwache wie unermüdliche Untersuperbösewicht Røbøt endlich in Doktor Metas geheimer Basis ankommt, ist Doktor Meta über die Verspätung und die übrige Ausbeute von 1-Cent-Stücken alles andere als erfreut. Um ihn in bessere Laune zu versetzen, erzählt Røbøt von den Begegnungen mit verschiedenen Bösewichten auf seinen Irrwegen durch das Tunnelsystem. Ein Superbösewicht hat genug von unzuverlässigen elektronischen Gehilfen und bietet deshalb eine große Menge von identischen Bauteilen vom Typ A für eine Preis von 1, 25 Euro pro Bauteil zum Verkauf an. Ein Mittelbösewicht hat den Plan ein Roboterheer aufzubauen noch nicht aufgegeben hat und ist verzweifelt auf der Suche nach Bauteilen von Typ A. Er ist bereit je zwei Bauteile vom Typ A für je drei Bauteile vom Typ B, die er selbst noch übrig hat, zu tauschen. Ein vom gleichen Bausatz stammende und ähnlich fehlkonstruierte Unterbösewicht hat sich ebenso mit einer Menge Geld im Gepäck verlaufen. Sein Plan ist es einen Unterbösewichtsgehilfen zu bauen, der den Weg für ihn findet. Dazu möchte er Bauteile vom Typ B zum Preis von 0, 875 Euro pro Bauteil erwerben. Plötzlich ist Doktor Meta nicht mehr schlecht gelaunt, er rechnet nach, dass er so einen Euro ganz einfach in 1, 05 Euro verwandeln kann. Damit Røbøt beim nächsten Mal eine solche Gelegenheit von selbst erkennt, möchte Doktor Meta einen geeigneten Algorithmus entwerfen. 4 Seien dazu Allgemein eine Menge von angebotenen Objekten a1 , . . . , an (im vorliegenden Fall Euros, Bauteile vom Typ A und Bauteile vom Typ B) gegeben und weiter eine Tabelle T , wobei T [i, j] jeweils angibt wieviele Einheiten von Objekt aj für ein Objekt ai angeboten werden. Im vorliegenden Fall ergibt sich also T [1, 2] = 0, 8, T [2, 3] = 1, 5 und T [3, 1] = 0, 875 und damit T [1, 2] · T [2, 3] · T [3, 1] = 1, 05 > 1. Modellieren Sie das Problem mit Hilfe von einem geeigneten Graphen und konstruieren Sie einen effizienten Algorithmus, der entscheidet ob es eine Folge von Objekten ai1 , . . . , aik gibt, sodass T [i1 , i2 ] · T [i2 , i3 ] · · · T [ik , i1 ] > 1. Analysieren Sie die Laufzeit Ihres Algorithmus. Musterlösung: Wir möchten das Problem mit Hilfe des Bellman-Ford-Algorithmus lösen. Dazu modellieren wir jeden Typ von Objekten (also Euros und Bauteilen) als einen Knoten des Graphen G = (V, E), und jede Tauschmöglichkeit zwischen zwei Typen von Objekten als eine Kante zwischen den entsprechenden Knoten. Würden wir die „Tauschkurse“ zwischen den Objekten direkt als Kantengewichte verwenden, müssten wir nun einen positiven Kreis finden, was Bellman-Ford nicht kann. Wir beobachten aber, dass T [i1 , i2 ] · T [i2 , i3 ] · · · · T [ik , i1 ] > 1 gleichbedeutend ist zu 1 1 1 · ··· · <1 T [i1 , i2 ] T [i2 , i3 ] T [ik , i1 ] Wenn wir nun beide Seiten logarithmieren ergibt sich log 1 1 1 + log · · · + log <0 T [i1 , i2 ] T [i2 , i3 ] T [ik , i1 ] Wir definieren daher das Kantengewicht c : E → R als c((ij , ik )) = log T [ij1,ik ] = − log T [ij , ik ]. Ein Kreis mit negativem Gewicht in diesem Graphen entspricht einer gesuchten Folge von Objekttypen, mit der sich Gewinn durch „Im-Kreis-Tauschen“ erzielen lässt. Fraglich ist noch, von welchem Knoten aus Bellman-Ford gestartet werden muss. Es ist nicht klar, dass ein Knoten existieren muss, von dem aus ein Pfad zu jedem negativen Kreis existiert. Daher fügen wir einen weiteren Knoten v0 und Kanten (v0 , vi ) für alle vi ∈ V hinzu, und setzen c((v0 , vi )) = 0 für alle vi ∈ V . Wir starten Bellman-Ford auf v0 . Sei n die Anzahl der Arten Objekten, dann hat G n Knoten und bis zu n2 Kanten. Die Zeitkomplexität 3 von Bellman-Ford liegt dann in O n . 5