Technische Universität Wien Institut für Computergraphik und Algorithmen Arbeitsbereich für Algorithmen und Datenstrukturen 184.263 Algorithmen und Datenstrukturen 1 VL 4.0 WS2006/07 Übungsblatt 4 für die Übung am Montag 15. bzw. Dienstag 16. Jänner 2007 Aufgabe 4.1 Beschreiben Sie in rund 10 Sätzen die grundlegende(n) Idee(n) hinter Hashtabellen, ihre Einsatzgebiete und ihre Vor- und gegebenenfalls auch Nachteile. Gehen Sie in Ihren Betrachtungen auch auf die Wahl einer guten Hashfunktionen und der richtigen Tabellengröße ein, sowie auf (mögliche) Probleme bei zu hohem Füllgrad (Verhältnis besetzte ↔ freie Plätze in der Tabelle). Aufgabe 4.2 Gegeben sei eine Hashtabelle mit externer Verkettung der Überläufer mit fester Größe m = 10, sowie die Hashfunktion h(x) = (x + 3) mod 7. • Ist diese Hashfunktion klug gewählt? Warum (nicht)? • Fügen Sie in diese Hashtabelle nacheinander die Zahlen 3, 13, 4, 2, 6, 16, 9, 11 und 0 ein. Zeichnen Sie den Zustand der Hashtabelle Schritt für Schritt auf und denken Sie anhand des Resultats nochmals über den ersten Punkt nach. • Würden Sie die Tabellengröße oder die Hashfunktion ändern, um die Situation zu verbessern? Begründen Sie Ihre Wahl! 2 Aufgabe 4.3 Gegeben sei eine Hashtabelle der Größe m = 11. Der Zugriff soll mittels Linearem Sondieren mit der folgenden Hashfunktion erfolgen: h0 (k) = k mod 11 Tragen Sie die folgenden Zahlen in dieser Reihenfolge in die Tabelle ein und zeichnen Sie jeweils den Zustand der Tabelle nach dem Eintragen jeder einzelnen Zahl: h5, 21, 18, 3, 39, 9, 32, 14i. Danach löschen Sie die Zahlen h21, 39, 3i. Schließlich fügen Sie noch die Zahlen h16, 7, 8i ein. Zeichnen Sie wieder den Zustand der Tabelle nach jeder Operation. Aufgabe 4.4 Gegeben ist eine Hashtabelle mit Quadratischem Sondieren, m = 10, h0 (k) = k mod m, c1 = 3, c2 = 5. • Fügen Sie die Zahlen der Folge h12, 13, 28, 10, 14, 32, 43i in der angegebenen Reihenfolge in diese Hashtabelle ein. Zeichnen Sie den Zustand der Tabelle nach dem Einfügen jeder einzelnen Zahl. • Zeigen Sie, dass die angegebene Hashfunktion nicht ideal ist. Tipp: Überlegen Sie, was passieren würde, wenn Sie statt 43 als letzte Zahl 42 einfügen würden. Verbessern Sie die Hashfunktion so, dass ein derartiges Problem nicht mehr auftreten kann. Aufgabe 4.5 Gegeben ist eine Hashtabelle mit Double Hashing, welche die Verbesserung nach Brent verwendet, m = 11, h1 (k) = k mod 11, h2 (k) = k mod 5 + 2 (beachten Sie, dass die Modulo-Operation stärker bindet als die Addition). Fügen Sie die Zahlen der Folge h12, 13, 28, 10, 14, 32, 43i in der angegebenen Reihenfolge in diese Hashtabelle ein. Zeichnen Sie den Zustand der Tabelle nach dem Einfügen jeder einzelnen Zahl. Aufgabe 4.6 1. Gegeben ist eine Hashtabelle in Form eines Arrays feld mit der festgelegten Größe m. Jedes Element feld [j], j = 0, . . . , m − 1, dieses Arrays besteht aus folgenden Komponenten: 3 • feld [j].schl enthält den Schlüssel des Datensatzes; • feld [j].daten enthält die eigentlichen Daten; • feld [j].zustand enthält einen der folgenden Werte: – besetzt: feld [j] enthält einen gültigen Datensatz; – frei: feld [j] ist frei und war nie besetzt; – wiederfrei: feld [j] war schon besetzt, ist aber wieder frei. Schreiben Sie eine Prozedur in Pseudocode, welche diese Hashtabelle für die Verwendung mit Linearem Sondieren korrekt und vollständig initialisiert, aber auch keine überflüssigen Initialisierungen vornimmt. 2. Nehmen Sie an, dass eine Hashfunktion b(k) existiert, die aus einem Schlüssel k einen Hashindex in der oben deklarierten Tabelle berechnet. Schreiben Sie eine Prozedur in Pseudocode, die den Datensatz mit dem Schlüssel gesucht aus der Tabelle entfernt, falls er enthalten ist. Zur Behandlung von Kollisionen ist die konstante Schrittweite c1 zu verwenden. 3. Die oben genannten Schlüssel sind Zeichenketten in der Form, dass Sie auf die einzelnen Elemente eines Schlüssels schl als Zeichen schl .zeichen[i], i = 0, . . . , n − 1, zugreifen können. Es existiert eine Funktion ord (z), welche den numerischen Wert eines Zeichens z liefert. Schreiben Sie eine Hashfunktion b0 (k) in Pseudocode, welche nach der Divisions-Rest-Methode vorgeht, um aus den Zeichen des Schlüssels k einen Hashindex zu berechnen. Aufgabe 4.7 Für einen Test aus Algorithmen und Datenstrukturen“ soll durch die Erstellung mehrerer ” gleichwertiger Beispielgruppen das durchaus beliebte Schummeln möglichst verhindert werden. Der Sitzplan eines Hörsaals wird als Graph G(V, E) realisiert. Dabei entspricht jeder Knoten aus V einer Person mit einem Angabeblatt, und jede Kante aus E verbindet jeweils zwei direkt benachbart (z.B. in einem Radius von maximal 2,5 Metern) sitzende Personen. Nun muss jeder Person eine Beispielgruppe (A, B, C, . . . ) so zugeordnet werden, dass an keiner Stelle des Hörsaals zwei Personen mit der gleichen Gruppe direkt benachbart sitzen. Die Erstellung zusätzlicher Beispielgruppen verursacht natürlich auch entsprechend mehr Arbeit. Daher ist das Ziel, mit möglichst wenigen Beispielgruppen trotzdem eine sichere Testsituation zu schaffen. 4 Schreiben Sie einen Algorithmus in ausführlichem Pseudocode, der die oben beschriebene Aufgabe für einen gegebenen Sitzplangraphen G mit Hilfe eines Verfahrens, das auf Tiefensuche (DFS) basiert, möglichst gut löst. Versuchen Sie dabei, jedem Knoten eine möglichst (lexikographisch) kleine Beispielgruppe zuzuweisen. Aufgabe 4.8 Gegeben sei ein zusammenhängender, ungerichteter Graph G(V, E), wobei jeder Kante e ∈ E ein positives Gewicht ce > 0 zugewiesen ist. Schreiben Sie detaillierten Pseudocode für eine Prozedur FindPath(...) (und gegebenenfalls verwendete Unterprozeduren) für folgendes Problem: Gesucht sind alle Pfade im Graphen G(V, E), die aus genau H Kanten bestehen und ein Gesamtgewicht (Summe über alle Kantengewichte ce des Pfades) von mindestens L besitzen. Geben Sie von einem solchen gefundenen Pfad den Start- und den Endknoten sowie das Gesamtgewicht aus. Ein Pfad darf mehrmals ausgegeben werden. Achten Sie besonders darauf, dass für eine Pfad gilt: • Ein Pfad ist zusammenhängend und • kreisfrei (d.h. in einem Pfad darf kein Knoten bzw. keine Kante mehrmals vorkommen). Aufgabe 4.9 (a) Gegeben ist ein ungerichteter, zusammenhängender Graph G = (V, E) durch folgende Adjazenzlisten-Darstellung: v 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: N (v) (4) (3) (4, 2) (5, 3, 1) (8, 6, 4) (10, 8, 5) (8) (10, 7, 6, 5) (10) (9, 8, 6) Zeichnen Sie den repräsentierten Graphen. 5 (b) Gegeben ist ferner folgender Algorithmus: Algorithmus Was-bin-ich? Input: Graph G = (V, E) Output: Kantenmenge F und Feld X[1 . . . |V |] 1: F = {}; 2: X[1 . . . |V |] = (−1, −1, . . . , −1); 3: A(4); Prozedur A(v) 1: X[v] = X[v] + 1; 2: für alle Knoten w ∈ N (v) in der gegebenen Ordnung { 3: falls X[w] < 0 dann { 4: F = F ∪ (v, w); 5: A(w); 6: } 7: X[w] = X[w] + 1; 8: } Welche Kanten liefert der Algorithmus für den gegebenen Beispielgraphen in F zurück? Markieren Sie diese Kanten in der Zeichnung zu ihrem Graphen. Welche Werte werden im Feld X zurückgeliefert? (c) Beschreiben Sie in einem Satz, was dieser Algorithmus allgemein für einen beliebigen ungerichteten, zusammenhängenden Graphen macht und in F bzw. X zurückliefert. Aufgabe 4.10 Breitensuche ist ein Verfahren zum Durchsuchen bzw. Durchlaufen von Knoten eines Graphen ähnlich der in der Vorlesung behandelten Tiefensuche. Auch hier geht man von einem Startknoten u aus, allerdings unterscheiden sich nun Tiefen- und Breitensuche bei der Reihenfolge, in der weitere Knoten des Graphen abgearbeitet bzw. besucht werden. Wir gehen im Folgenden bei diesem Beispiel von einem ungerichteten Graphen aus. Ausgehend vom Startknoten u werden bei der Breitensuche zunächst alle adjazenten Knoten besucht, d.h. alle Knoten v, für die eine Kante (u, v) im Graphen existiert; zusätzlich werden alle Knoten v in einer Warteschlange gespeichert. Die Breitensuche bearbeitet also zuerst immer alle direkt benachbarten Knoten und folgt nicht – wie die Tiefensuche – gleich einem Pfad in die Tiefe. Nachdem nun alle adjazenten Knoten von u betrachtet wurden, wird der erste Knoten der Warteschlange entnommen und für diesen das Verfahren rekursiv wiederholt. Dies wird nun so lange fortgesetzt, bis entweder die Warteschlange leer ist, d.h. alle Knoten besucht wurden, bzw. bis – wenn man nach einem bestimmten Knoten sucht – dieser gefunden wurde. 6 Wie auch bei der Tiefensuche werden durch Markierungen bereits bearbeiteter Knoten Mehrfachbesuche von Knoten verhindert. Gegeben sei nun die Datenstruktur Queue (Warteschlange), welche eine beliebige Menge an Objekten aufnehmen und diese in der Reihenfolge ihres Einfügens wieder zurück liefern kann. Folgende Operationen sind auf der Queue definiert: • enqueue(X): Fügt ein Objekt X in die Queue ein. • dequeue(): Entfernt das älteste Objekt aus der Queue und liefert es zurück. Benutzen Sie die Queue, um eine nicht rekursive Version von Breitensuche zu entwerfen. Beschreiben Sie erst in wenigen Worten den Ablauf Ihres Algorithmus und geben Sie ihn dann in Pseudocode an. Die Queue können Sie dabei als “Black Box” betrachten, d.h. Sie können sie benutzen, ohne die genaue Funktionsweise explizit als Pseudocode ausarbeiten zu müssen.