Datenstrukturen

Werbung
Datenstrukturen
Mariano Zelke
Sommersemester 2012
Graphen
(Link)
(a) Ein ungerichteter Graph G = (V , E ) besteht aus einer endlichen
Menge
und einer Teilmenge
V von Knoten (engl.: vertices)
E ⊆ {u, v } u, v ∈ V , u 6= v von Kanten (engl.: edges).
I
I
Die Endpunkte u, v einer ungerichteten Kante {u, v } sind
gleichberechtigt.
u und v heißen dann Nachbarn.
Wir sagen auch: u und v sind adjazent.
(b) Für die Kantenmenge E eines gerichteten Graphen G = (V , E ) gilt
E ⊆ {(u, v ) | u, v ∈ V , u 6= v }.
I
I
Mariano Zelke
Der Knoten u ist Anfangspunkt und der Knoten v Endpunkt der
Kante (u, v ).
v heißt auch ein direkter Nachfolger von u und u ein direkter
Vorgänger von v .
Datenstrukturen
2/18
Wichtige Begriffe
Sei G = (V , E ) ein gerichteter oder ungerichteter Graph.
I
Eine Folge (v0 , v1 , ..., vm ) heißt ein Weg in G ,
falls für jedes i (0 ≤ i < m) gilt
I
I
(vi , vi+1 ) ∈ E (für gerichtete Graphen) bzw.
{vi , vi+1 } ∈ E (für ungerichtete Graphen).
Die Weglänge ist m, die Anzahl der Kanten. Ein Weg heißt einfach,
wenn kein Knoten zweimal auftritt.
I
Ein Weg heißt ein Kreis, wenn v0 = vm und (v0 , ..., vm−1 ) ein
einfacher Weg ist. G heißt azyklisch, wenn G keine Kreise hat.
I
Ein ungerichteter Graph heißt zusammenhängend, wenn je zwei
Knoten durch einen Weg miteinander verbunden sind.
Mariano Zelke
Datenstrukturen
3/18
Das Königsberger Brückenproblem
1
2
3
4
Mariano Zelke
Datenstrukturen
4/18
Graph-Implementierungen
I
I
Welche Datenstruktur sollten wir für die Darstellung
eines Graphen G wählen?
Welche Operationen sollen schnell ausführbar sein?
I
I
Ist e eine Kante von G ? Die Adjazenzmatrix wird sich als eine gute
Wahl herausstellen.
Bestimme die Nachbarn, bzw. Vorgänger und Nachfolger eines
Knoten: Die Adjazenzlistendarstellung ist unschlagbar.
Besonders die Nachbar- und Nachfolgerbestimmung ist wichtig, um
Wege zu durchlaufen.
Mariano Zelke
Datenstrukturen
5/18
Die Adjazenzmatrix
Für einen Graphen G = (V , E ) (mit V = {0, ..., n − 1}) ist
1
wenn {u, v } ∈ E (bzw. wenn (u, v ) ∈ E ),
AG [u, v ] =
0
sonst
die Adjazenzmatrix AG von G .
Eine Kantenfrage ist (u, v ) eine Kante?“ wird sehr schnell
”
beantwortet, nämlich in Zeit O(1).
Die Bestimmung aller Nachbarn oder Nachfolger eines Knoten v ist
hingegen langwierig:
I
I
Die Zeile von v muss durchlaufen werden.
Zeit Θ(n) selbst dann, wenn v nur wenige Nachbarn hat.
Speicherplatzbedarf Θ(n2 ) auch für Graphen mit relativ wenigen
Kanten:
Die Datenstruktur passt sich nicht der Größe des Graphen an!
Mariano Zelke
Datenstrukturen
6/18
Die Adjazenzliste
G wird durch ein Array A von Listen dargestellt.
Die Liste A[v ] führt alle Nachbarn von v auf, bzw. alle Nachfolger von v
für gerichtete Graphen.
Die Nachbar- bzw. Nachfolgerbestimmung für Knoten v gelingt in
Zeit proportional zur Anzahl der Nachbarn oder Nachfolger.
Der benötigte Speicherplatz ist O(n + |E |): Die Datenstruktur passt
sich der Größe des Graphen an.
Für die Beantwortung der Kantenfrage ist (u, v ) eine Kante?“
”
muss die Liste A[u] durchlaufen werden: Die benötigte Zeit ist also
proportional zur Anzahl der Nachbarn oder Nachfolger.
Da die Nachbar- bzw. Nachfolgerbestimmung für das Durchlaufen von
Wegen benötigt wird, ist die sich der Größe des Graphen anpassende
Adjazenzliste oft die Datenstruktur der Wahl.
Mariano Zelke
Datenstrukturen
7/18
Suche in Graphen
Wie durchsucht man ein Labyrinth?
I
Können wir das Preorder-Verfahren benutzen?
I
I
I
Preorder terminiert nicht, wenn ein ungerichteter Graph einen Kreis
besitzt: Preorder erkennt nicht, dass es Knoten bereits besucht hat!
Um Preorder anzupassen, sollten wir für jeden Knoten v vermerken,
ob v bereits besucht wurde.
Wir erhalten Tiefensuche = Preorder + Knotenmarkierung“.
”
Mariano Zelke
Datenstrukturen
8/18
Tiefensuche: Die globale Struktur
I
Der gerichtete oder ungerichtete Graph G werde durch seine
Adjazenzliste A repräsentiert.
I
Im Array besucht wird vermerkt, welche Knoten bereits besucht
wurden.
void Tiefensuche(){
for (int k = 0; k < n; k++) besucht[k] = 0;
for (int k = 0; k < n; k++)
if (! besucht[k])
tsuche(k);
}
I
Jeder Knoten wird besucht,
I
aber tsuche(v) wird nur dann aufgerufen, wenn v nicht als
besucht“ markiert ist.
”
Mariano Zelke
Datenstrukturen
9/18
tsuche()
Die Knoten des Graphen werden definiert durch
struct Knoten {
int name;
Knoten *next; }
I
Als erste Aktion von tsuche(v) wird v als besucht markiert.
I
Dann wird tsuche(v) für alle unmarkierten Nachbarn/Nachfolger
von v rekursiv aufgerufen.
void tsuche(int v){
besucht[v] = 1;
Knoten *p;
for (p = A[v]; p !=0; p = p->next)
if (!besucht [p->name])
tsuche(p->name);
}
Mariano Zelke
Datenstrukturen
10/18
Der Wald der Tiefensuche: ungerichtete Graphen
Wir veranschaulichen das Vorgehen von Tiefensuche.
I
Eine Kante {v , w } ∈ E heißt eine Baumkante, falls tsuche(w) in der
Schleife von tsuche(v) aufgerufen wird.
I
Eine Kante {v , w } ∈ E heißt eine Rückwärtskante, falls {v , w }
keine Baumkante ist und tsuche(w) während der Ausführung von
tsuche(v) aufgerufen wird.
Die Baumkanten definieren einen Wald, den wir den Wald der
Tiefensuche nennen.
I
I
Wir können uns die (eigentlich ungerichteten) Kanten von WG auch
gerichtet vorstellen, jede Kante in WG vom früher zum später
markierten Knoten gerichtet. Dann können wir in WG von
Nachfolgern, Nachfahren, Vorgängern und Vorfahren sprechen.
Sind alle Kanten des Graphen entweder Baum- oder Rückwärtskanten?
Mariano Zelke
Datenstrukturen
11/18
Tiefensuche: Beispiel
(und noch eins hier)
@ Nordturm
V
@
A 54
V53
A V55@
@
H
H
B
B
V35 A
V17
V52
V32 B
B
H
@ H
H
H @
B
@
@ V51
@ V B V1
V
@
@ 16 B
@P 31
PP V @
PP 15 @
P
P
V30
Ostturm
V37
V36 @
@
A V38 @
@
H
H
@
@
@
@ V
@
@
@
@
@
@
V18
@
39
@
@
@
@
@
@
@
@
@
V19
H
H
@ V40
V2
V3 A H
H V20 @
@
@
A V4 @
@
@
V14
V0
V5
V21
V6
V22
Innenhof
V29
V13
@
@
@
A V12
@
H
H V
11
V
@
@ 28
V50 @
V10
@
@
@ V27 V49
@
V34 @
V26
@
@
V46
@ V48 @
@
V
47
@
@
Westturm
Mariano Zelke
P
P
PP
P
@
@ V7 PP
B
@
@
V23
B V8 V41@
P
V9 B
PP
@
@
P @
@
BB V24
V42
B
V33
V25
B
H
B
H
@ V45 BB
V43
@
A
V44
@
Südturm @
Datenstrukturen
12/18
Tiefensuche: Beispiel
(und noch eins hier)
54
53
52
37
55
35
51
17
18
19
16
1
2
3
0
4
20
30
14
5
21
29
13
6
22
28
12
7
23
34
11
10
9
8
27
26
25
24
46
47
41
33
45
Datenstrukturen
39
40
15
48
Mariano Zelke
32
38
31
50
49
36
42
43
44
12/18
Tiefensuche: Beispiel
(und noch eins hier)
54
53
52
37
55
35
51
Baumkante
Rückwärtskante
17
18
19
16
1
2
3
0
4
20
30
14
5
21
29
13
6
22
28
12
7
23
34
11
10
9
8
27
26
25
24
46
47
41
33
45
Datenstrukturen
39
40
15
48
Mariano Zelke
32
38
31
50
49
36
42
43
44
12/18
Tiefensuche: Beispiel
(und noch eins hier)
54
53
52
37
55
35
51
Wald der
Tiefensuche:
17
18
19
16
1
2
3
0
4
20
30
14
5
21
29
13
6
22
28
12
7
23
34
11
10
9
8
27
26
25
24
46
47
41
33
45
Datenstrukturen
39
40
15
48
Mariano Zelke
32
38
31
50
49
36
42
43
44
12/18
Tiefensuche für ungerichtete Graphen I
Sei G = (V , E ) ein ungerichteter Graph und {v , w } sei eine Kante von
G . WG sei der Wald der Tiefensuche für G .
(a) tsuche(v ) werde vor tsuche(w ) aufgerufen. Dann ist w ein
Nachfahre von v in WG .
(b) G besitzt nur Baum- und Rückwärtskanten.
(a) Warum ist w ein Nachfahre von v in WG ?
I
I
I
tsuche(v ) wird vor tsuche(w ) aufgerufen: Knoten w ist zum
Zeitpunkt der Markierung von Knoten v unmarkiert.
tsuche(v ) kann nur dann terminieren, wenn w markiert wird.
w muss irgendwann während der Ausführung von tsuche(v) markiert
werden.
(b) Warum besitzt G nur Baum- und Rückwärtskanten? Wenn {v , w }
eine Kante ist, dann ist v Vorfahre oder Nachfahre von w .
Mariano Zelke
Datenstrukturen
13/18
Tiefensuche für ungerichtete Graphen II
Tiefensuche besucht jeden Knoten genau einmal.
I
Das Programm Tiefensuche wird von einer Schleife gesteuert, die
tsuche(v) für alle noch nicht besuchten Knoten v aufruft.
I
Wenn aber tsuche(v) aufgerufen wird, dann wird v sofort markiert:
Nachfolgende Besuche sind ausgeschlossen.
Der Baum von v in WG enthält genau die Knoten der
Zusammenhangskomponente von v in G . Die Bäume von WG
entsprechen genau den Zusammenhangskomponenten von G .
I
T sei ein Baum im Wald WG und T besitze v als Knoten.
I
v erreicht jeden Knoten in T , denn T ist zusammenhängend.
Also ist die Zusammenhangskomponente von v in G eine Obermenge der
Knotenmenge von T .
I
Wenn v = v0 , v1 , . . . , vs = u ein Weg in G ist, dann gehören v0 , v1 , . . . , vs
zum selben Baum von WG . Die Komponente von v ist also auch eine
Untermenge der Knotenmenge von T . Also sind sie gleich.
Mariano Zelke
Datenstrukturen
14/18
Labyrinthe
Tiefensuche löst jedes Labyrinth-Problem, das sich als ungerichteter
Graph interpretieren lässt.
I
Wenn es möglich ist, vom Eingang den Ausgang zu erreichen, dann
befinden sich Eingang und Ausgang in derselben
Zusammenhangskomponente.
I
Dann besitzt der Baum in WG mit dem Eingangsknoten einen Weg
von Eingangs- zum Ausgangsknoten.
Wie schnell findet man aus einem Labyrinth heraus?
Wie schnell ist Tiefensuche?
Mariano Zelke
Datenstrukturen
15/18
Die Laufzeit von Tiefensuche
Tiefensuche terminiert nach höchstens O(|V | + |E |) Schritten.
I
Zuerst muss der Aufwand für die Schleife in Tiefensuche bestimmt
werden: O(n) Schritte.
I
Wie viele Schritte werden direkt von tsuche(v) ausgeführt?
O(grad(v )) Operationen, wobei grad(v ) die Anzahl der Nachbarn
von v ist.
I
Wie viele Operationen werden insgesamt ausgeführt?
X
X
X
O(
(1 + grad (v ) ) = O(
1+
grad (v ))
v ∈V
v ∈V
v ∈V
= O(|V | + |E |).
Tiefensuche ist sehr schnell.
Mariano Zelke
Datenstrukturen
16/18
Anwendungen der Tiefensuche
Sei G = (V , E ) ein ungerichteter Graph. Dann kann in Zeit O(|V | + |E |)
überprüft werden,
(a) ob G zusammenhängend ist:
I
I
I
G ist genau dann zusammenhängend, wenn G genau eine
Zusammenhangskomponente hat.
Die Bäume von WG entsprechen genau den
Zusammenhangskomponenten von G .
G ist also genau dann zusammenhängend, wenn WG aus genau
einem Baum besteht, d.h. wenn tsuche(0) alle Knoten besucht.
(b) ob G ein Wald ist:
I
I
Mariano Zelke
G ist genau dann ein Wald, wenn G keine Rückwärtskanten hat.
Überprüfe, dass für jede Kante {v , w } entweder tsuche(w) direkt in
tsuche(v) aufgerufen wird oder dass tsuche(v) direkt in tsuche(w)
aufgerufen wird.
Datenstrukturen
17/18
Tiefensuche für gerichtete Graphen I
Wald der
Tiefensuche:
0
1
2
4
0
1
3
4
2
3
Angenommen, die Tiefensuche startet im Knoten 0 und in jeder
Adjazenzliste sind die Knoten aufsteigend sortiert. Dann erhalten wir vier
verschiedene Kantentypen:
I
I
I
I
Baumkanten: (0, 1), (0, 2) und (2, 3), sie bilden den Wald WG der
Tiefensuche
Rückwärtskante: (3, 0), sie verbindet einen Knoten mit seinem
Vorgänger in WG
Querkanten: (3, 1) und (4, 2), sie verbinden zwei Knoten, die in WG
nicht miteinander in einer Nachfolger-Vorgänger-Beziehung stehen.
Vorwärtskante: (0, 3), sie verbindet einen Knoten mit einem
Nachfahren in WG , der kein Kind ist.
Mariano Zelke
Datenstrukturen
18/18
Herunterladen