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 8. Ü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 (Breitensuche, 4 Punkte) Gegeben sei folgender gerichteter Graph mit der Knotenmenge {A, B, C, D, E, F, G}: A B D C E F G In diesem Graph werde nun eine Breitensuche durchgeführt, und zwar ausgehend vom Knoten A. Wählen Sie die Knoten bei mehreren Möglichkeiten jeweils in alphabetischer Reihenfolge aus. (Am Ende des Übungsblattes finden Sie weitere Kopien dieses Graphs zum anmalen. Wenn Sie diese nutzen, kennzeichnen Sie bitte eindeutig, welche Kopie die Lösung zu welcher Teilaufgabe enthält.) a) Zählen Sie die Knoten des Graphen in einer Reihenfolge auf, in der diese von einer Breitensuche jeweils zum ersten Mal berührt werden. Heben Sie im Graph außerdem alle Kanten (z. B. farbig) hervor, die bezüglich dieser Breitensuche tree-Kanten sind. b) Sind im Graph bzgl. dieser Breitensuche irgendwelche backward-Kanten vorhanden? Wenn ja, heben Sie diese hervor. c) Sind im Graph bzgl. dieser Breitensuche irgendwelche cross-Kanten vorhanden? Wenn ja, heben Sie diese hervor. d) Sind im Graph bzgl. dieser Breitensuche irgendwelche forward-Kanten vorhanden? Wenn ja, heben Sie diese hervor. Musterlösung: Die Knoten werden von einer Breitensuche in der Reihenfolge A, B, C, E, D, G, F besucht. Im folgenden Graphen sind die tree- bzw. Baumkanten als normale Linien, die cross- bzw. Querkanten als gestrichelte Linien und die einzige backward- bzw. Rückwärtskante als gepunktete Linie eingezeichnet. Bei einer Breitensuche können keine forward- bzw. Vorwärtskanten auftreten. 1 A B D C E F G Aufgabe 2 (Graphrepräsentationen, 4 Punkte) Ein gerichteter Graph G = (V, E) liege je in der Kantenfolgen-, Adjazenzfeld-, Adjazenzlisten- und Adjazenzmatrixrepräsentation ohne zusätzliche Informationen vor. Wie lange dauert es ausgehend von den verschiedenen Repräsentationen jeweils den Ausgangs- und getrennt davon den Eingangsgrad aller Knoten zu bestimmen? Skizzieren Sie, wie die jeweiligen Algorithmen funktionieren. Musterlösung: Kantenfolge Sowohl für das Bestimmen des Eingangs-, als auch des Ausgangsgrades muss die Liste der Kanten jeweils komplett abgelaufen werden. Währenddessen hält man für jeden Knoten je einen mit 0 initialisierten Zähler bereit, und inkrementiert diesen jeweils, wenn eine eingehende (bzw. ausgehende) Kante für diesen Knoten gefunden wurde. Die Laufzeit ist somit O(|V | + |E|). Adjazenzfeld Das Bestimmen des Ausgangsgrades ist hier effizient: Da das Feld V für jeden Knoten den Index der ersten ausgehenden Kante in E speichert, lässt sich der Ausgangsgrad von v mittels V [v + 1] − V [v] bestimmen. Um den Ausgangsgrad aller Knoten zu bestimmen ist also ein Aufwand von O(|V |) nötig. Im Kantenfeld E sind jeweils die Zielknoten gespeichert, Durchlaufen von E und zählen wie oft die Knoten jeweils vorkommen liefert daher den Eingangsgrad. Da der Zähler für die Eingangsgrade der Knoten initialisiert werden muss, ergibt sich insgesamt eine Laufzeit von O(|V | + |E|). Adjazenzliste Für doppelt zusammenhängende Listen ohne zusätzliche Informationen (wie in der Aufgabenstellung gefordert) ergibt sich für Eingangs- wie Ausgangsgrad O(|V | + |E|). Für den Ausgangsgrad muss jeweils die Länge der angehängten Liste bestimmt werden. Da die Länge aller Listen sich zu |E| addiert (ein Listenelement pro Kante), ergibt sich die besagte Laufzeit. Für den Eingangsgrad müssen alle Listen durchlaufen werden und gezählt werden wie oft die Knoten jeweils vorkommen. (Mit in der in der Vorlesung behandelten Erweiterungen geht das effizienter: Im Kapitel über Listen wurde vorgestellt, dass man sich manchmal die Länge der verketteten Liste im Listenkopf speichert, um effizienten Zugriff auf die Listenlänge zu haben. In diesem Fall wäre dann die Bestimmung des Ausgangsgrades eines Knotens in O(1), für alle Knoten damit in 2 O(|V |). Wendet man nun außerdem die Modifikation der Adjazenzliste von Vorlesungsfolie 22 an, so hat jeder Knoten nicht nur eine Liste der ausgehenden, sondern auch eine Liste der eingehenden Kanten. Wenn wiederum die Listenlänge gespeichert wird, fällt damit auch die Komplexität der Bestimmung des Eingangsgrades auf O(|V |).) Adjazenzmatrix Zur Bestimmung des Ausgangs-(bzw. Eingangs-)grades eines Knotens müssen alle Einträge in der Zeile (bzw. Spalte) dieses Knoten aufsummiert werden. Es gibt |V | solche Einträge. 2 Damit ergibt sich über alle Knoten eine Komplexität von O |V | . Aufgabe 3 (Algorithmenentwurf, 4 Punkte) Ein gerichteter Graph G = (V, E) heißt einzeln zusammenhängend, wenn es für alle Knoten u, v ∈ V höchstens einen Pfad von u nach v gibt. Geben Sie einen effizienten Algorithmus an, der bestimmt, ob ein gerichteter Graph einzeln zusammenhängend ist. Musterlösung: Zunächst sei angemerkt, dass ein einzeln zusammenhängender Graph per Definition immer azyklisch sein muss: Gibt es einen Kreis, so gibt es für jeden Knoten v auf dem Kreis und jeden k ∈ N je einen Pfad, der den Kreis k-fach abläuft. Somit gibt es beliebig viele Pfade von v nach v, was der Definition widerspricht. Der Algorithmus startet auf jedem Knoten des Graphen eine Tiefensuche. Der Graph ist einzeln zusammenhängend, wenn es in jeder der Tiefensuchen nur Baum- und keine Quer-, Rückwärts- oder Vorwärtskanten gibt. Beweis: Zunächst zeigen wir, dass G azyklisch sein muss, wenn der Algorithmus ausgibt, dass G einzeln zusammenhängend ist. Wir nehmen also an, dass G einen Kreis enthält. Betrachten wir eine Tiefensuche, die auf einem Knoten u des Kreises gestartet wurde. Klar ist, dass diese Tiefensuche irgendwann die Kante betrachtet, die den Kreis schließt, also eine Kante (v, u), wobei v der Knoten ist, der auf dem Kreis vor u liegt. Da u aber die Wurzel der Tiefensuche ist, ist diese Kante klar eine Rückwärtskante und der Algorithmus bricht ab. Wir zeigen nun zunächst, dass der Algorithmus immer mit „nicht einzeln zusammenhängend“ abbricht, wenn der Graph nicht einzeln zusammenhängend ist. Angenommen, es gibt zwei Knoten u und w, und zwei Pfade P = hu, v1 , v2 , . . . , wi und P 0 = hu, v10 , v20 , . . . , wi mit P 6= P 0 . Betrachten wir nun die Tiefensuche, die in u startet. O.b.d.A. nehmen wir an, dass der Pfad P von der Tiefensuche vor dem Pfad P 0 gefunden wurde. Sei e = (a, b) die erste Kante in P 0 , in der P und P 0 sich unterscheiden. Falls b in P ist, so ist e eine Vorwärtskante und der Algorithmus bricht ab. Falls b nicht in P ist, so beginnt in b ein Teilpfad P̃ = hb1 , . . . , bk i von P 0 , der nicht zu P gehört, aber irgendwann (spätestens in w) wieder mit P zusammenlaufen muss. Die Tiefensuche exploriert also P̃ und stößt irgendwann auf eine Kante, die wieder zu P führt. Diese Kante ist eine Querkante und der Algorithmus bricht ab. Wir müssen nun noch zeigen, dass keine der Tiefensuchen eine Quer-, Rückwärts- oder Vorwärtskante findet, wenn der Graph einzeln zusammenhängend ist. Rückwärtskanten Sei e = (u, v) eine Rückwärtskante. Dann gibt es also einen Pfad P von v nach u, und somit schließt e einen Kreis. Vorwärtskanten Sei e = (u, v) eine Vorwärtskante. Dann gibt es einen Pfad P von u nach v, der nicht über e verläuft und damit zusammen mit e insgesamt mindestens zwei Pfade. Querkanten Sei e = (u, v) eine Querkante in einer Tiefensuche, die in w gestartet wurde. Dann gibt es einen Pfad P1 von w nach u und einen Pfad P2 von w nach v. Indem man zunächst P1 und dann e abläuft ergibt sich ein weiterer, von P2 verschiedenen Pfad von w nach v. Somit arbeitet der Algorithmus korrekt. Es müssen |V | Tiefensuchen ausgeführt werden. Die Laufzeit des Algorithmus ist damit O(|V | · (|V | + |E|)). 3 Aufgabe 4 (Doktor Meta, 4 Punkte) Der ebenso geniale wie fundamental fehlentworfene Robotergehilfe und Untersuperbösewicht Røbøt hat sich auf dem Weg zu Doktor Metas geheimer Basis in den Tunnelsystemen unter Karlsruhe verlaufen. Bei sich hat er nur eine große Menge von 1-Cent-Stücken, die er ursprünglich als Beute bei Doktor Meta abliefern wollte. Modellieren Sie das Tunnelsystem als ungerichteten Graphen G = (V, E). Dabei entsprechen Kreuzungen den Knoten in V und Tunnelabschnitten zwischen Kreuzungen den Kanten. Entwerfen Sie einen Algorithmus der in Zeit O(|V | + |E|) einen Pfad berechnet, der jede Kante in E in beide Richtungen genau je einmal traversiert. Wie kann Røbøt mit Hilfe dieses Algorithmus und der Münzen das Hauptquartier von Doktor Meta finden? Gehen Sie dabei davon aus, dass sich Røbøt zwar den Algorithmus merken kann, sein beschränktes Gedächtnis aber keine zurückgelegten Wege und offensichtlich keine Karte speichern kann. Musterlösung: Hierfür kann eine Variante der Tiefensuche auf G = (V, E) verwendet werden. Wir nehmen den Graph als zusammenhängend an (sonst kann es so einen Pfad nicht geben). Jede Kante wird dabei bei der ersten und der zweiten Traversion jeweils mit einer eindeutigen Kennzeichnung versehen. Wie bei der Tiefensuche werden außerdem bereits besuchte Knoten markiert und sich zu jedem Knoten die Kante gemerkt, durch die er zum ersten Mal betreten wurde. Der Algorithmus beginnt an einem beliebigen Knoten und führt von dort eine Tiefensuche auf G aus. Der gesuchte Pfad ergibt sich schließlich aus der Reihenfolge, in der die Kanten während der Tiefensuche betrachtet werden. Zunächst wird der Startknoten als besucht markiert und die folgenden Schritte ausgehend vom Startknoten durchgeführt. 1. Abgehend vom aktuellen Knoten wird (falls möglich) eine beliebige unmarkierte Kante {u, v} betrachtet. Diese Kante wird als einmal besucht markiert. • Falls v noch nicht markiert ist (und damit zum ersten Mal besucht wird), wird {u, v} als einmal besucht markiert, als Eingangskante zu v gespeichert und zum Pfad hinzugefügt. v wird markiert und mit v als aktuellen Knoten wird der erste Schritt wiederholt. • Falls v als bereits besucht markiert ist, wird {u, v} als zweimal besucht markiert und (diesmal in umgekehrter Richtung) zum Pfad hinzugefügt. Der erste Schritt wird erneut mit u als aktuellem Knoten ausgeführt. 2. Falls es keine unmarkierte Kante {u, v} (mehr) gibt, wird u über die Kante {w, u} über die u zum ersten Mal betreten wurde wieder verlassen. Die Kante {w, u} wird als zweimal besucht markiert und zum Pfad hinzugefügt. Der erste Schritt wird mit w als aktuellen Knoten wiederholt. Offensichtlich werden so alle Kanten in jede Richtung höchstens einmal traversiert. Da der Graph zusammenhängend und nicht gerichtet ist, werden bei der Tiefensuche alle Knoten irgendwann betrachtet (der Algorithmus kann nicht in eine Sackgasse führen, da jeder Knoten durch die Eingangskante wieder verlassen werden kann bis schließlich der Startknoten wieder erreicht wird). Da jeder Knoten erneut besucht wird, solange er benachbart zu unmarkierten Kanten ist (erst wenn alle Kanten markiert sind kann er endgültig durch den zweiten Schritt verlassen werden), werden auch alle Kanten besucht. Jede Kante ist außerdem entweder Eingangskante zu einem Knoten und wird daher durch den zweiten Schritt erneut in umgekehrter Reihenfolge durchlaufen oder wird direkt im ersten Schritt in beide Richtung durchlaufen. Deshalb werden alle Kanten auch tatsächlich genau einmal in jede Richtung traversiert. Die Laufzeit der Tiefensuche liegt in O(|V | + |E|). Røbøt kann einfach von der nächsten Kreuzung beginnen den beschriebenen Algorithmus auszuführen und abbrechen, sobald er Doktor Metas geheime Basis erreicht hat. Dafür muss er jeden Tunnelabschnitt höchstens zweimal betreten (wenn er die Markierungen geschickt setzt). Mit Hilfe der Münzen muss er markieren welche Kreuzungen er bereits besucht hat (dazu kann er z.B. eine Münze in die Mitte der Kreuzung legen) und durch welchen Tunnelabschnitt er die Kreuzung zum ersten Mal betreten hat (dazu kann er z.B. zwei übereinandergestapelte Münzen in Richtung dieses Abschnittes legen (ein gedachter 4 Pfeil von der Münze in der Tunnelmitte zu diesem Münzstapel zeigt dann in Richtung des besagten Tunnelabschnittes). Außerdem muss er markieren welche Kante er zum ersten beziehungsweise zweiten Mal betreten hat. Da jede Kante (bzw. jeder Tunnelabschnitt) entweder eine Eingangskanten ist (und damit durch den Münzstapel indirekt als bereits besucht markiert) oder direkt zum zweiten Mal besucht wird (und Røbøt in diesem Fall einfach den gleichen Tunnelabschnitt zurücklaufen muss), müssen nur zum zweiten Mal besuchte Kanten explizit markiert werden. Um Laufarbeit zu sparen sollte das nach dem Zurücklaufen passieren, z.B. durch drei aufeinander gestapelte Münzen am Tunnelabschnittseingang. 5