Lösungsvorschlag Hausübung 8 Peter Kling 16. Juli 2007 Aufgabe 27 Heapsort (vgl. Alg. 1) aus der Vorlesung. Illustrieren Heapsort am Beispiel des Arrays A =< 1, 3, 6, 9, 5, 8 >. Betrachten Sie den Algorithmus Sie die Arbeitsweise von Algorithmus 1 Heapsort(A) 1: 2: 3: 4: 5: 6: Build-Heap(A) for i ← length[A] downto 2 do A[1] ↔ A[i] heap-size[A] ← heap-size[A] − 1 Heapify(A, 1) end for Lösung Stellt man das Array in der üblichen Baumstruktur dar, ergibt sich folgendes Bild: A =< 1, 3, 6, 9, 5, 8 > Zunächst wird in Zeile 1 ein Maxheap aus dem Array A erstellt (vgl. Abbildung 1). Elemente die innerhalb des Heaps liegen (d.h. deren Indexposition ≤ heap-size[A] ist) sind im Folgenden grün markiert. Danach folgt durch die for-Schleife in Zeile 2 bis 6 die eigentliche Sortierung des Arrays. Dabei wird die Gröÿe des Heaps in jedem Schritt um eins verkleinert. 1 Abbildung 1: Erzeugung eines Heaps aus A A =< 6, 5, 8, 3, 1, 9 > Da durch die Vertauschung die Heap-Eigenschaft verletzt wurde, stellt nun Zeile 5 diese wieder her (nur im grünen Teil des Baumes!). A =< 8, 5, 6, 3, 1, 9 > Die restlichen Schritte verlaufen analog (vgl. Abbildung 2). Man erhält also schlieÿlich das sortierte Array 2 A =< 1, 3, 5, 6, 8, 9 >. Abbildung 2: Sortierung mittels Heapsort 3 Aufgabe 28 Auf dem Liborifest gibt es eine neue Attraktion: die Halle der Spiegel, ein Irrgarten. Der Bauplan kann dem Aufgabenblatt entnommen werden. a) Modellieren Sie die Halle der Spiegel mit Hilfe eines Graphen G = (V, E), und stellen Sie diesen mit Hilfe einer Adjazenzliste dar. Nutzen Sie dazu die im Bauplan verwendeten Raumnummern. b) Sei r die Anzahl der Räume und t die Anzahl an Türen. Geben Sie einen Algorithfindpath(G, e, a) in Pseudocode an, der bei Eingabe sowohl des Bauplanes G auch des Eingangs e und Ausgangs a einen Weg von e nach a ausgibt, auf dem mus als die kleinste Anzahl Türen durchschritten werden muss. Ihr Algorithmus soll dabei Laufzeit O(r + t) haben. Lösung a) Zur Modellierung wird ein ungerichteter Graph verwendet. Die Räume werden als Knoten und die Türen als Kanten dargestellt. Die Raumnummern entsprechen dabei den jeweiligen Knotennummern. Formal gilt für den Graphen G = (V, E) also: V = {1, 2, . . . , 19} und ={ E {1, 5}, {3, 4}, {3, 6}, {4, 7}, {5, 9}, {6, 10}, {7, 11}, {8, 12}, {9, 8}, {9, 10}, {9, 14}, {10, 11}, {10, 15}, {12, 16}, {13, 14}, {13, 17}, {14, 15}, {14, 18}, {15, 19}, {16, 17}, {17, 18}, {18, 19} } e a = 19. Man könnte hier auch noch den Eingang und den Ausgang als zusätzliche Knoten bzw. a modellieren. Der Einfachheit halber setzen wir hier aber e=1 und Die zugehörige Adjazenzliste ist in Abbildung 3 gegeben. Bemerkung. Da wir hier einen einfachen Graphen und keinen Multigraphen zur Modellierung benutzen, werden doppelte Türen (wie z.B. zwischen Raum 17 und 18) zu einer Kante zusammengefasst. b) Algorithmus 2 löst das Problem mittels Breitensuche. In Zeile 1 wird zunächst eine Breitensuche auf π G mit Startknoten e durchgeführt. Diese liefert zwei Arrays d bzw. r = |V |. Schlieÿlich wird ein Array pfad erstellt, in dem der jeweils der Gröÿe zu berechnende Pfad gespeichert werden soll. Dessen Berechnung erfolgt nun über eine einfache for-Schleife (Zeile 5 bis 7) anhand der Daten, die uns die Breitensuche geliefert hat. Der Berechnete Pfad wird schlieÿlich in Zeile 8 zurück gegeben. 4 1 5 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 4→6 3→7 1→9 3 → 10 4 → 11 9 → 12 5 → 8 → 10 → 14 6 → 9 → 11 → 15 7 → 10 8 → 16 14 → 17 9 → 13 → 15 → 18 10 → 14 → 19 12 → 17 13 → 16 → 18 14 → 17 → 19 15 → 18 Abbildung 3: Adjazenzliste zum Graphen der Halle der Spiegel Algorithmus 2 ndpath(G=(V,E),e,a) 1: (d, π) ← BFS(G, e) {Breitensuche; liefert Distanzarray d und Vorgängerarray π } 2: dist ← d[a] {Distanz zwischen Ein- und Ausgang} 3: pfad ← new Array[1 . . .dist+1] 4: pfad[dist+1] ← a {Pfad endet im Ausgang} 5: for i ←dist downto 1 do 6: pfad[i] ← π[pfad[i + 1]] {Baue Pfad Schritt für Schritt aus den Vorgängern auf} 7: end for 8: return pfad 5 Laufzeitanalyse • • • Zeile 1: BFS benötigt Zeit Zeilen 2,4,8: O(1) Zeile 3: Der Abstand zwischen zwei Knoten ist durch also • O(|V | + |E|) = O(r + t) |E| = t beschränkt, O(t) Zeilen 5 bis 7: Analog zu Zeile 3 folgt auch hier Insgesammt ergibt sich also eine Laufzeit von O(t) O(r + t). Aufgabe 29 Ein Graph G = ({1, 2, . . . , 10}, E) sei durch die Adjazenzliste in Abbildung 4 gegeben. 1 2 3 4 5 6 7 8 9 10 8 → 10 6 1→6→7 10 4 → 10 7 2 9 8 3 Abbildung 4: Adjazenzliste zur Aufgabe 29 a) Zeichnen Sie den angegebenen Graphen. Wenden Sie dann den Tiefensuchalgorithmus startend bei Knoten 1 auf den Graphen an und geben Sie zu jedem Knoten v die Discovering/Finishing-Times (d[v], f [v]) an. Benutzen Sie dabei die in der Adjazenzliste angegebene Reihenfolge. b) Geben Sie an, welche Kanten Baum-, Rückwärts-, Vorwärts- oder Kreuzungskanten sind. Lösung Der Graph ist in Abbildung 5 zeichnerisch wieder gegeben. In den Abbildungen 6 und 7 kann das Vorgehen der Tiefensuche von links-oben nach rechts-unten verfolgt werden. Discovering- (blau) bzw. Finishing-Times (grün) sind jeweils an den Knoten oben bzw. unten vermerkt. Die roten Kanten stellen den Tiefensuchwald dar, der bei der Durchführung des Algorithmus berechnet wird. Bei ihnen handelt es sich also um die Baumkanten. 6 Abbildung 5: Graph zu Aufgabe 29 Abbildung 8 stellt den Graphen einschlieÿlich der unterschiedlichen Kantenarten dar. Baumkanten sind Rot, Rückwärtskanten Grün, Vorwärtskanten Blau und Kreuzungskanten Gelb eingefärbt. Wie zu sehen ist handelt es sich um einen echten Tiefelsuchenwald, welcher aus 3 Komponenten besteht. 7 cc Abbildung 6: Tiefensuche (Teil 1) 8 Abbildung 7: Tiefensuche (Teil 2) 9 Abbildung 8: Graph zu Aufgabe 29 (mit verschiedenen Kantenarten) Aufgabe 30 Beweisen Sie, dass ein beliebiger Graph mit |E|) |V | Knoten und |E| Kanten in Zeit O(|V | + auf die Eigenschaft Kreisfreiheit getestet werden kann. Lösung Idee: Das derzeitige Vorlesungsthema und die verlangte Laufzeit legen nahe, dass man hier auf einen der beiden Graphenalgorithmen zurückgreifen sollte: Breiten- oder Tiefensuche. Man wird allerdings feststellen, dass die Ergebnisse die diese Liefern (Distanzen und Vorgänger im Falle der Breitensuche; Entdeckungs- und Endzeiten bei der Tiefensuche) einem hier nicht direkt weiterhelfen. Eine itterierte Anwendung (wie z.B. in der Präsenzübung 25) kommt aufgrund der geforderten Laufzeit nicht in Frage. Betrachtet man sich nun aber z.B. das Vorgehen der Tiefensuche, kann man erkennen, dass Kreise dazu führen, erneut auf graue Knoten zu stoÿen. Deshalb werden wir im Folgenden einen leicht modizierten Tiefensuchalgorithmus verwenden und zeigen, dass dieser die gegebene Problemstellung lösen kann. Betrachte die Algorithmen DFS(G) und DFS_Visit(u) (Vorlesung am 18.06.2007, Folie 21). Wir ändern die beiden Algorithmen folgendermaÿen ab: DFS-Visit(u): Falls ein zu u adjazenter Knoten grau ist, gebe foundCircle zurück. Diese Abfrage kann sinnvoller Weise in die for-Schleife (Zeile 4,5) eingefügt werden. DFS(G): Falls der Aufruf von DFS-Visit(u) foundCircle zurück liefert, foundCircle. Beendet der AlgorithDFS-Visit(u) liefert foundCircle), gebe in Zeile 4 beende den Algorithmus mit der Ausgabe mus hingegen normal (kein Aufruf von GraphHasNoCircle zurück. 10 Der geänderte DFS-Algorithmus wird im Folgenden mit DFS-CircleTest bezeichnet. Wie zu erkennen ist, testet der veränderte Algorithmus ob er beim Aufstellen des Tiefensuchwaldes ein sich in Bearbeitung bendender Knoten angetroen wird. Dieser wird uns schlieÿlich einen Kreis liefern. Zunächst ist klar, dass diese Änderungen die Laufzeit nicht beeinussen (eingefügte Operationen haben konstante Laufzeit). Die Laufzeit von G = (V, E) beträgt folglich auch O(|V | + |E|). DFS-CircleTest bei Eingabe Nun bleibt noch folgendes Lemma zu zeigen: Lemma 1. DFS-CircleTest(G) liefert foundCircle genau dann, wenn der Graph G einen Kreis enthält. Beweis. ⇒: DFS-CircleTest(G) liefere also foundCircle. Das bedeutet, dass ein Aufruf von DFS_Visit für einen Knoten u ∈ V foundCircle geliefert hat. Also wurde bei der Aufstellung des Tiefensuchenwaldes wärend der Abarbeitung von u ein bereits grauer Nachbar v ∈ V von u entdeckt. Insbesondere folgt damit, dass d[v] < d[u] < f [v]. Satz 33 (Klammersatz zur Tiefensuche) aus der Vorlesung liefert nun, dass u Nachfolger von v im Tiefensuchenwald ist. Das heiÿt es gibt einen Pfad von v nach u im Tiefensuchenwald und damit auch in G. Die Kante (u, v) schlieÿt somit einen Kreis in G. ⇐: C = (u0 , u1 , . . . , ur , u0 ) (o.B.d.A. ui 6= uj ∀i 6= j). Weiterhin sei o.B.d.A. u0 der erste Knoten aus C , den die Tiefensuche entdeckt. Die Tiefensuche beendet den Knoten u0 erst, wenn alle von ihm aus erreichbaren Knoten entdeckt (und abgearbeitet) wurden. Insbesondere wird ur also vor dem Zeitpunkt f [u0 ] entdeckt. Es gilt also: G enthalte nun also einen Kreis d[u0 ] < d[ur ] < f [u0 ] Das bedeutet Aufruf mit Knoten u0 u0 ur ist grau bei der Bearbeitung von die Kreiskante (ur , u0 ) ur . Folglich wird DFS-Visit beim und damit den zu diesem Zeitpunkt grauen entdecken. D.h. es wird der Wert foundCircle zurück gegeben und der Algorithmus beendet. Zusammen folgt damit die Behauptung. Bemerkung. • Etwas allgemeiner ausgedrückt ndet DFS-CircleTest(G) Rückwärtskanten des Graphen G im Tiefensuchenwald. Damit zeigt obiges Lemma insbesondere, dass ein Graph genau dann Kreisfrei ist, wenn ein Tiefensuchenwald keine Rückwärtskanten enthält. Anschaulich sieht man dies auch sehr schön in Abbildung 8 (die grünen Kanten schlieÿen allesamt Kreise). • Man hätte sich hier auch direkt bei Lemma 36 aus der Vorlesung bedienen können, welches im wesentlichen obige Aussage enthält. 11 Wir haben also durch Angabe eines Algorithmus gezeigt, dass ein Graph auf die Eigenschaft Kreisfreiheit in Zeit O(|V | + |E|) getestet werden kann. 12