Lösungsvorschlag Hausübung 8

Werbung
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
Herunterladen