Humboldt-Universität zu Berlin Institut für Informatik Prof. Dr. Ulf Leser M. Bux, B. Grußien, J. Sürmeli, S. Wandelt Berlin, den 29.6.2015 Übungen zur Vorlesung Algorithmen und Datenstrukturen Übungsblatt 6 Abgabe: Montag den 13.7.2015 bis 11:10 Uhr vor der Vorlesung im Hörsaal oder bis 10:45 Uhr in den Fächern im Raum RUD25 4.402. Die Übungsblätter sind in Gruppen von 2 Personen zu bearbeiten. Sie können auf diesem Übungsblatt bis zu 50 Punkte erhalten. Zur Erinnerung: Jedes Übungsblatt muss bearbeitet werden. Sie müssen mindestens ein Blatt für wenigstens eine Aufgabe jedes Übungsblattes abgeben. Die Lösungen sind auf nach Aufgaben getrennten Blättern abzugeben. Vermerken Sie auf allen Abgaben Ihre Namen, Ihre Matrikelnummern, den Namen Ihrer Goya-Gruppe und welchen Übungstermin bei welchem Übungsleiter Sie besuchen. Heften Sie bitte die zu einer Aufgabe gehörenden Blätter vor der Abgabe zusammen. Beachten Sie auch die aktuellen Hinweise auf der Übungswebsite unter: https://u.hu-berlin.de/alg_ds_ss15_u Konventionen: • Mit der Aufforderung “Analysieren Sie die Laufzeit” ist hier gemeint, dass Sie eine möglichst gute obere Schranke der Zeitkomplexität angeben sollen und diese begründen sollen. Aufgabe 1 (AVL-Bäume-Schreibtischtest) 7 + 7 = 14 Punkte Führen Sie einen Schriebtischtest für die folgenden zwei Aufgaben zu AVL-Bäumen aus. 1. Sei T ein leerer AVL-Baum. Fügen Sie nacheinander die Schlüssel 5, 17, 89, 23, 13, 2, 28, 26, 102, 18, 19, 117 in T ein und zeichnen Sie den jeweiligen AVL-Baum nach jeder insert-Operation. 2. Entfernen Sie nacheinander die Schlüssel 75, 87, 70 aus dem unten gegebenen AVL-Baum und zeichnen Sie den jeweiligen AVL-Baum nach jeder delete-Operation. Falls ein Knoten mit zwei Kindern gelöscht wird, dann soll mit dem symmetrischen Vorgänger (siehe Folie 32 im Foliensatz Suchbäume) getauscht werden. Aufgabe 2 (Optimale Suchbäume) 6 Punkte Sei S = {s1 , . . . , sn } eine Menge von paarweise verschiedenen Schlüsseln und seien h1 , . . . , hn paarweise verschiedene absolute Häufigkeiten, mit denen auf diese Schlüssel zugegriffen wird, d. h., Schlüssel si wird hi -mal abgefragt. Weiterhin nehmen wir an, dass es keine Zugriffe auf Schlüssel außerhalb von S gibt. Betrachten Sie den folgenden Greedy-Algorithmus zur Konstruktion eines binären Suchbaums: Wähle den Schlüssel x, auf den am häufigsten zugegriffen wird, als Wurzel. Von den Schlüsseln, die kleiner sind als x und als linkes Kind von x in Frage kommen, ohne gegen die Suchbaumeigenschaft zu verstoßen, wähle denjenigen, auf den am häufigsten zugegriffen wird, als linkes Kind von x. Analog wähle von den Schlüsseln, die größer sind als x und deren Einfügen als rechtes Kind von x die Suchbaumeigenschaft nicht verletzen würde, den Schlüssel, auf den am häufigsten zugegriffen wird, als rechtes Kind von x. Fahre iterativ fort, bis alle Schlüssel eingefügt wurden. Unter der Suchbaumeigenschaft ist zu verstehen, dass für jeden Knoten v im Suchbaum gilt, dass die Schlüssel aller Knoten im linken Teilbaum von v kleiner und im rechten Teilbaum von v größer als der Schlüssel von v sind. Zum Beispiel würde der o.g. Greedy-Algorithmus für die Schlüssel 10, 2, 12 und 20 mit den absoluten Zugriffshäufigkeiten 100, 20, 10 und 90 den folgenden binären Suchbaum generieren. Beweisen oder widerlegen Sie, dass der obige Algorithmus unter den genannten Bedingungen einen optimalen Suchbaum erzeugt. Aufgabe 3 (Tries, Implementierung) 4 + 4 + 4 = 12 Punkte Zur Speicherung einer Menge von Wörtern zum Zweck eines effizienten Zugriffs haben Sie in der Vorlesung Tries kennengelernt. Dabei handelt es sich um Bäume, deren Kanten mit Buchstaben beschriftet sind, wodurch gemeinsame Präfixe unterschiedlicher Wörter nur einmal gespeichert werden müssen. Aufgrund dieser Eigenschaft werden Tries auch als Präfixbäume bezeichnet. Um in einem solchen Trie das Ende eines Wortes, welches zugleich das Präfix eines anderen Wortes ist, erkennen zu können, kann man sich des Tricks bedienen, an jedes zu speichernde Wort ein $-Zeichen anzuhängen, wodurch das Ende eindeutig markiert ist. Im Folgenden finden Sie ein Beispiel für einen Trie, in den die Wörter i, in, inn, so, son und sky eingefügt wurden: s $ $ n o i k y $ $ n $ n $ Ihre Aufgabe ist es, die Implementierung eines solchen Tries zu vervollständigen. Nutzen Sie hierzu die auf der Homepage zur Verfügung gestellte Datei Trie.java und ergänzen Sie die unten aufgeführten Methoden um fehlenden Code. An bestehendem Code soll nichts verändert werden. Sie dürfen aber die main()-Methode zum Testen verwenden und auch um Ihre eigenen Testfälle erweitern. 1. Implementieren Sie die Methode Trie.add(String word), die ein gegebenes Wort in den bestehenden Baum einfügt. 2. Implementieren Sie die Methode Trie.contains(String word), die überprüft, ob ein gegebenes Wort im Baum vorhanden ist, und entsprechend true oder false zurückgibt. 3. Implementieren Sie die Methode Trie.printWords(), die alle im Trie enthaltenen Wörter ausgibt. Hinweis: Ihr Java-Programm muss auf dem Rechner gruenau2 kompilieren und laufen. Die Abgabe dieser Aufgabe erfolgt über Goya. Aufgabe 4 (Graphalgorithmen, Dijkstra) 7 + 6 + 3 + 2 = 18 Punkte 1. Stellen Sie sich vor, dass es in Ihrer Lieblingsstadt keine Verkehrszeichen gibt – d. h. an allen Kreuzungen muss die Regel rechts-vor-links beachtet werden. Außerdem nehmen wir an, dass an jeder Kreuzung maximal 4 Straßen aufeinander treffen. Somit ist klar definiert, was in Fahrtrichtung rechts, links und geradeaus ist. Wir modellieren die Straßen der Stadt wie folgt als ungerichteten Graphen: Jede Kreuzung wird ein Knoten des Graphen und jede Straße, die zwei Kreuzungen verbindet, wird eine Kante. Wir nehmen an, dass Straßen unterschiedlich schnell durchfahren werden können und dies modellieren wir mit nichtnegativen Kantenkosten. Hier ein Beispiel: h 1 d 1 1 i e 2 8 a 2 f 2 b 5 6 4 c g Rechts-vor-links-Kreuzungen haben die folgende Eigenschaft: Will man rechts abbiegen, so muss man auf niemanden achten, will man geradeaus fahren, so muss man auf den Verkehr von rechts achten, will man links abbiegen, so muss man auf den Verkehr von rechts und von geradeaus achten. Wir führen deshalb noch Kosten für das Abbiegen ein: Muss man auf niemanden achten, dann kostet das Überfahren der Kreuzung 1, muss man auf eine Richtung achten, dann kostet das Überfahren der Kreuzung 2, muss man auf zwei Richtungen achten, dann kostet das Überfahren der Kreuzung 3. Zum Beispiel würde der Pfad e → f → i im obigen Graph 8 + 3 + 2 kosten, der Pfad a → b → c kostet 2 + 1 + 4, der Pfad e → h → i kostet 1+1+1. Im Folgenden nehmen Sie bitte an, dass die Abbiegekosten von x über y nach z durch die Funktion AK(x, y, z) berechnet wird. Sie brauchen diese Funktion nicht implementieren! Lösen Sie nun die beiden folgenden Teilaufgaben. a) Angenommen, der Algorithmus von Dijkstra auf Foliensatz 13, Folie 26 wird wie folgt modifiziert: new_dist ergibt sich nicht nur aus der Distanz A[k] + w, sondern auch die Abbiegekosten werden addiert. Genauer: Sei y der Knoten, der zuletzt die Distanz zu k aktualisiert hat. Als Distanz A[f ] werden die Distanz zu A[k], die Kantenkosten w der Kante (k, f ) und die Abbiegekosten des Pfads von y über k nach f summiert, falls dies eine Verbesserung darstellt. Zeigen Sie, dass die beschriebene Modifikation des Dijkstra-Algorithmus nicht die korrekten kürzesten Pfade ausgehend von einem Startknoten x berechnet. b) Beschreiben Sie detailliert, wie ein gegebener Graph G modifiziert werden kann, um die Abbiegekosten direkt in die Kantenkosten zu integrieren, damit der unmodifizierte Dijkstra-Algorithmus die korrekten Pfadkosten für das Szenario mit Abbiegekosten berechnet. 2. Sei G = (V, E) ein ungerichteter, zusammenhängender Graph. Dann ist e ∈ E eine Brückenkante, falls der Teilgraph, der durch Entfernen von e aus G entsteht, nicht mehr zusammenhängend ist. Der unten angegebene Graph enthält genau eine solche Brückenkante e. a) Entwerfen Sie einen Algorithmus, der für einen gegebenen Graph G = (V, E) alle Brückenkanten berechnet. Notieren Sie Ihren Algorithmus als Pseudocode. b) Analysieren Sie die Laufzeit Ihres Algorithmus in Abhängigkeit von |V | und |E|. Hinweis: Es geht bei dieser Aufgabe nur darum, einen korrekten Algorithmus anzugeben; Ihr Algorithmus muss nicht optimal sein.