2.6 Datenstrukturen für Graphen

Werbung
2.6 Datenstrukturen für Graphen
Anwendungen überall.
Ungerichtete Kanten drücken symmetrische Beziehungen
aus, gerichtete Kanten auch asymmetrische Beziehungen.
G = (V, E)
V endliche Knotenmenge (vertices, nodes),
n := |V |,
E Kantenmenge (edges), m := |E|.
Ungerichtete Kante {v, w }, gerichtete Kante (v, w ).
wenn Verwechslungen
ausgeschlossen sind, auch (v, w ).
v und w adjazent, wenn zwischen ihnen eine Kante verläuft.
v und e inzident, wenn v Endknoten von e.
. – Seite 147/726
1
1
2
4
5
3
6
8
2
7
4
5
3
6
7
8
d(v) Grad (degree) = Anzahl ind(v ) Ingrad = Anzahl eingehender Kanten.
inzidenter Kanten.
d(5) = 3
outd(v ) Outgrad = Anzahl ausgehender Kanten.
ind(5)= 1, outd(5)= 2
(1,2,5,7,3) ungerichteter Weg (1,2,3,7,5) gerichteter Weg
der Länge 4.
der Länge 4.
. – Seite 148/726
Wege heißen einfach, wenn höchstens Anfangs- und
Endknoten übereinstimmen.
Kreise sind einfache Wege mit Anfangsknoten = Endknoten
und Länge > 1 für gerichtete Graphen und Länge > 2 für
ungerichtete Graphen.
Graphen ohne Kreise heißen azyklisch.
. – Seite 149/726
Ungerichtet
Gerichtet
u ≈ v :⇔
u ≈ v :⇔
∃ Weg zwischen u und v . ∃ Weg von u nach v und
∃ Weg von v nach u.
– ∀v ∈ V : v ≈ v .
– ∀u, v ∈ V : u ≈ v ⇒ v ≈ u.
– ∀u, v, w ∈ V : u ≈ v, v ≈ w ⇒ u ≈ w.
Also ist ≈ in beiden Fällen eine Äquivalenzrelation ⇒
V zerfällt in disjunkte Äquivalenzklassen, d. h.
V = V1 ∪˙ · · · ∪˙ Vk , alle Knoten in Vi sind äquivalent, aber
keine Knoten aus Vi und Vj sind äquivalent, falls i 6= j .
. – Seite 150/726
Ungerichtete Graphen: Äquivalenzklassen heißen
Zusammenhangskomponenten (connected components),
sie sind die nicht vergrößerbaren Mengen von paarweise
verbundenen Knoten.
Im Beispiel: V1 = {1, 2, 3, 4, 5, 7}, V2 = {6, 8}.
Zusammenhängende, azyklische, ungerichtete Graphen
heißen auch Bäume.
Gerichtete Graphen: Äquivalenzklassen heißen starke
Zusammenhangskomponenten (strongly connected
components), sie sind die nicht vergrößerbaren Mengen
von Knoten, so dass es von jedem Knoten zu jedem
anderen einen gerichteten Weg gibt.
Im Beispiel: V1 = {1, 2, 3, 5, 7}, V2 = {4}, V3 = {6}, V4 = {8}.
. – Seite 151/726
Wenn wir Kanten (i, j) als i ≤ j interpretieren, bilden genau
die azyklischen Graphen eine partielle Ordnung.
Deren Knotenmenge kann topologisch sortiert werden.
(s. Kap. 2.3).
Adjazenzmatrix A: A(i, j) = 1, falls (i, j) ∈ E (gerichtet)
oder {i, j} ∈ E (ungerichtet), A(i, j) = 0 sonst.
Speicherplatz für n2 Bits (Arraydarstellung möglich).
Adjazenzlisten:
Array der Knoten, für Knoten i Liste der
j mit (i, j) ∈ E (gerichtet) oder {i, j} ∈ E
(ungerichtet).
Speicherplatz n für das Array und m bzw. 2m Listeneinträge.
. – Seite 152/726
Operation:
Adjazenzmatrix:
Ist (i, j) ∈ E ?
O(1)
Berechne d(i). Θ(n)
Adjazenzlisten:
O(d(i))
Θ(d(i))
Meistens Darstellung durch Adjazenzlisten.
Traversieren von Graphen und Erzeugung einer nützlichen
Kantenpartition
Tiefensuche
(DFS, depth first search)
und
Breitensuche
(BFS, breadth first search).
. – Seite 153/726
Tiefensuche in ungerichteten Graphen
Informell: Versuche einen Weg so lang wie möglich auszudehnen,
ohne Kreise zu schließen. Wenn es nicht weitergeht,
Backtracking, neuer Start am Vorgänger (← Stack
für aktuellen Weg).
Kanteneinteilung:
Baumkanten (Treekanten) erreichen Knoten
erstmalig, alle anderen Rückwärtskanten
(Backkanten), da sie im Baum zurückführen.
DFS(v), gestartet bei leerem Stack, erreicht alle w mit
v ≈ w und ordnet ihnen eine DFS-Nummer zu.
Es wird also die Zusammenhangskomponente von v
bearbeitet, d. h. Neustart bei allen anderen Knoten nötig:
. – Seite 154/726
Datenstrukturen:
Graph beschrieben durch Adjazenzlisten.
Array P (Predecessor) für T -Vorgänger, mit
Nullen initialisiert.
Array num der Länge n für DFS-Nummern, mit
Nullen initialisiert.
Listen für die Mengen T (Treekanten) und
B (Backkanten), zu Beginn leer.
Variable i für Integers, zu Beginn 0.
→ O(n)
Rahmenprogramm: Für x ∈ V :
if num(x) = 0 then P (x) := 0 und DFS(x).
. – Seite 155/726
DFS(v): i := i + 1, num(v) := i. (Die nächste freie Nummer wird
an v vergeben.)
w noch nicht gelesen
Durchlaufe Adj(v) ↓
für w:Falls num(w) = 0, (v, w) → T, P (w) := v , DFS(w).
Falls num(w) 6= 0 und w = P (v), tue nichts.
→ es ist (w, v) in T
Falls num(w) 6= 0, num(w) < num(v) und
w 6= P (v), (v, w) → B .
Falls num(w) 6= 0, num(w) > num(v), tue nichts.
Die Kanten in T und B sind gerichtet!
. – Seite 156/726
T - Kanten
1
2
8
ignoriert
1
1
Adj(1)
3
2
2
B -Kanten
3
3
3
4
7
2
Adj(2)
5
13
4
5 7
6
4
7
6
6
4
5
7
Adj(3)
125
7
5
9
8
2
Adj(4)
8
Adj(5)
23
Neustart
8
Adj(6)
Adj(7)
Adj(8)
Stop
23
6
. – Seite 157/726
Analyse:
– Jede Kante {v, w} wird genau einmal von v und genau
einmal von w betrachtet (→ O(n + m)).
– Jede Kante {v, w} wird in genau einer Richtung
entweder T - oder B -Kante.
– Die T -Kanten bilden einen Wald (Menge von Bäumen),
dessen Bäume die Knoten der
Zusammenhangskomponenten zusammenfassen.
. – Seite 158/726
Jeder DFS(v)-Aufruf gibt DFS-Nummer an v , danach kein
neuer DFS(v)-Aufruf. Jede Adjazenzliste wird genau einmal
durchlaufen (→ Behauptung 1).
Sei {v, w} ∈ E , o. B. d. A. wird v früher erreicht
→ DFS(v) startet
→ w wird über {v, w} erstmals gefunden → (v, w) ∈ T .
→ w wird anders gefunden, dann num(w) > num(v).
→ w wird anders gefunden, dann entweder
– num(w) < num(v) und w 6= P (v) → (v, w) ∈ B oder
– num(w) > num(v).
. – Seite 159/726
Weiterhin Fall num(w) > num(v). . .
DFS(w) innerhalb von DFS(v), also wird v in Adj(w) vor w in
Adj(v) gefunden → (w, v) ∈ B , da v 6= P (w) und
num(v) < num(w).
Später wird w in Adj(v) gefunden → (v, w) 6∈ B ,
da num(v) < num(w) (→ Behauptung 2).
Knoten bei Neustart im Rahmenprogramm erhält keinen
T -Vorgänger. Alle anderen erhalten genau einen
T -Vorgänger.
Die T -Kanten sind azyklisch, da (v, w) ∈ T
⇒ num(v) < num(w) (→ Behauptung 3).
Ungerichtete Graphen sind genau dann azyklisch, wenn
DFS keine B -Kante erzeugt.
. – Seite 160/726
Schematisierung des zeitlichen Ablaufs für {v, w} ∈ E :
Wenn DFS(w) vor DFS(v) begonnen wird, wird DFS(w)
später als DFS(v) beendet.
DFS(v )
w in Adj(v ) gefunden
Möglichkeiten und Klassifikation von DFS(w )
unmöglich, da v in Adj(w )
v in Adj(w) gefunden
→ (w, v) ∈ T
→ (v, w) ∈ B
→ (w, v) ∈ B
→ (v, w) ∈ T
. – Seite 161/726
Tiefensuche in gerichteten Graphen
Informell: T -Kanten wie bisher, bei Start in v kommen alle
von v aus erreichbaren Knoten in den T -Baum
mit Wurzel v .
Weitere Einteilung:
B -Kanten führen im Baum zu einem Vorgänger.
F -Kanten (Forward) führen im Baum zu einem
Nachfolger.
C -Kanten (Cross) sind die restlichen Kanten, sie
führen innerhalb eines Baumes zu einem früheren
Teilbaum oder zu einem früher konstruierten Baum.
. – Seite 162/726
Formal:


 0
α(v) =
1

 2
bis DFS(v) begonnen wird (äquivalent num(v) = 0),
während DFS(v),
nach Beendigung von DFS(v).
Es wird w in Adj(v) gefunden.
(1) num(w) = 0 → (v, w) T -Kante.
(2) num(w) 6= 0, num(w) > num(v) → (v, w) F -Kante, da w
innerhalb DFS(v) entdeckt wurde.
(3) num(w) 6= 0, num(w) < num(v), α(w) = 1
→ (v, w) B -Kante, da v innerhalb DFS(w) entdeckt wurde.
(4) Sonst, d. h. num(w) 6= 0, num(w) < num(v), α(w) = 2
→ (v, w) C -Kante, da v nach Beendigung von DFS(w)
entdeckt wurde.
. – Seite 163/726
7
7 4
4
2
3
3 1
1
6
5
13
5 12 6
10 11
8
9
9 8 14
2
Adj(1) 3
8
Adj(2)
Adj(3)
Adj(4)
Adj(5)
4
5
9
7
17
7
3 13
Adj(6)
Adj(7)
19
Adj(8)
6
Adj(9)
Neustart
Jede Kante wird einmal verarbeitet →
O(n + m).
. – Seite 164/726
Schematisierung des zeitlichen Ablaufs für (v, w):
DFS(v)
w in Adj(v) gefunden
Möglichkeit für DFS(w)
(v, w) ∈ C
(v, w) ∈ B
(v, w) ∈ F
(v, w) ∈ T
Unmöglich. Wenn DFS(w) später
als DFS(v) beginnt, wird DFS(w)
nicht durch DFS(v) unterbrochen.
. – Seite 165/726
Breitensuche
Informal: Vom Startpunkt aus werden alle w ∈ Adj(v) zuerst
nummeriert und in eine Queue geschrieben.
Solange die Queue nicht leer ist, wird BFS für
das nächste Element der Queue aufgerufen.
10
13
11
5
6
2
1
12
7
8
3
4
9
Die Knoten werden in der Reihenfolge ihres Abstandes vom Startknoten gefunden.
O(n + m)
. – Seite 166/726
Herunterladen