# Graphen
## Grundlagen
- Bestehen aus `Knoten` und `Kanten`
- Anwendungen: Routenplanung, soziale Netzwerke
### Typen von Graphen
- `Vollstaendiger Graph`: Jeder Knoten ist mit jedem anderen Knoten verbunden
- `Zusammenhaengender Graph`: Man kann jeden Knoten ueber die Kanten erreichen
- `gewichtet` / `ungewichtet`
- `gerichtet` / `ungerichtet`
## Speicherung von Graphen
### Adjazenzmatrix
- zweidimensionales Integer-Array mit Groesse n*n
- `x`: Kante mit Gewicht x / `NaN`: Kante existiert nicht
- **Beispiel:** `matrix[5][1] = 10` -> Kante von Knoten `5` zu `1` mit Gewicht `10`
- `matrix[0][1] = NaN` -> Kante von Knoten `0` zu `1` nicht vorhanden
#### Vorteile:
- schnelles Abfragen von Kanten (`O(1)`)
- schnelles Hinzufuegen/Loeschen von Kanten (`O(1)`)
#### Nachteile:
- Knoten hinzufuegen/loeschen sehr aufwendig
- Enormer Speicherverbrauch, da auch ungenutzte Kanten abgespeichert werden (`O(n²)`)
### Adjazenzliste
- Liste von Knoten, wobei fuer jeden Knoten die direkten Nachfolgeknoten mit ihrem jeweiligen Gewicht abgespeichert werden
| Knoten | Verbindungen |
|---|---|
| A | (B, Gewicht: 17) |
| B | (A, Gewicht: 12), (C, Gewicht: 954) |
| C | (C, Gewicht: 4) |
#### Vorteile
- Speichereffizent: Nur tatsaechlich vorhandene Kanten nutzen Speicherplatz
- Nachfolger eines Knotens finden geht schneller
#### Nachteile
- Pruefen, ob zwei bestimmte Knoten verbunden sind, geht langsamer
## Graphalgorhitmen
### Graph durchlaufen
#### Pseudocode
```
markiere alle Knoten als nicht besucht
markiere v als besucht
füge v in eine (zunächst leere) Datenstruktur D ein
solange D nicht leer ist
entnehme einen Knoten w aus D
für alle Kanten {w, u}
falls u als nicht besucht markiert ist
markiere u als besucht
füge u in D ein
```
#### Erläuterung
- nutzt man für `Datenstruktur D` eine `Queue`, führt man eine `Breitensuche` durch.
- nutzt man einen `Stack`, eine `Tiefensuche`
##### Breitensuche (Queue)
- Es wird zunächst in die Breite gesucht, d.h. alle nacheinander alle verbundenen Knoten, dann die Knoten, die an diesen verbunden sind, usw. (`Welle`)
##### Tiefensuche (Stack)
- Es wird zunächst in die Tiefe gesucht, d.h. es wird damit begonnen, einen Pfad komplett bis zum Ende zu durchlaufen, bevor abzweigende Pfade betrachtet werden. (`Oktopusarm`)
### Moore
- Ermittelt Abstand eines jeden Knotens vom Startknoten bei einem `ungewichteten` Graphen
- Da der Vorgänger gespeichert wird, kann der kürzeste Weg zu einem Knoten leicht nachvollzogen werden.
#### Pseudocode
```
für alle Knoten w
setze d(w) = ∞, vorg(w) = { }
füge v in eine (zunächst leere) Queue D ein
setze d(v) = 0
solange D nicht leer ist
entnehme einen Knoten w aus D
für alle Kanten {w,u}
falls d(u) = ∞
setze d(u) = d(w)+1, vorg(u) = w
füge u in D ein
```
### Dijkstra
- Ermittelt Abstand eines jeden Knotens vom Startknoten bei einem `gewichteten` Graphen
#### Pseudocode
```
für alle Knoten w
setze dg(w) = unendlich
setze dg(v) = 0
füge v in eine (zunächst leere) Priority-Queue D ein
solange D nicht leer ist
entnehme einen Knoten w mit minimalem dg(w) aus D
für alle Kanten {w,u}
falls dg(u) = unendlich
füge u in D ein
setze dg(u) = dg(w)+g({w,u})
falls dg(u) > dg(w)+g({w,u})
setze dg(u) = dg(w)+g({w,u})
```
### Topologische Sortierung
- Der Graph muss frei von Zyklen sein, damit eine topologische Sortierung gefunden werden kann.
- Die topologische Sortierung ist eine Reihenfolge, in der alle Knoten des Graphen abgearbeitet werden können, wobei die Abhängigkeiten berücksichtigt werden.
#### Pseudocode
```
Für jeden Knoten k des Graphen:
setze den Wert des Knoten auf die Anzahl an Knoten, die auf ihn zeigen
Setze topologische_Sortierung auf leer
Wiederhole solange es einen unmarkierten Knoten mit Wert 0 gibt
Nimm einen unmarkierten Knoten k mit Wert 0
Markiere k
Füge k der topologische_Sortierung hinzu
Für jeden Nachbarknoten n von k:
reduziere den Wert des Knoten n um 1
Falls noch unmarkierte Knoten übrig sind:
=> es gibt keine topologische Sortierung
sonst:
=> topologische_Sortierung ist gefunden
```