Graphensuche

Problembeschreibung

Eingabe (Input): Ein gerichteter oder ungerichteter Graph $G= (V,E)$ und ein Start-Knoten $s\in V$

Ziel: Identifiziere die Knoten aus $V$, die von $s$ aus in $G$ erreichbar sind.

Anwendungen

  • Überprüfen der "Verbundenheit" (connectivity)
  • Kürzeste Pfade im Graphen
  • Planung
  • Connected Components

Man unterscheidet zwei fundamentale Suchstrategien

  • Breitensuche (breadth-first search - BFS)
  • Tiefensuche (depth-first search - DFS)

GenericSearch


Input: Graph $G=(V,E)$ and a vertex $s \in V$
Postcondition: a vertex is reachable from $s$ if and only if it is marked as "explored"


mark $s$ as explored, all other vertices as unexplored
while there is an edge $(v,w) \in E$ with $v$ explored and $w$ unexplored do
 choose some edge $(v,w)$ // underspecified
 mark $w$ as explored

Proposition der Korrektheit des generischen Graphsuchalgorithmus

Nach Abschluss des generischen Graphsuchalgorithmus ist ein Vertex $v \in V$ genau dann als erkundet (explored) markiert, wenn und nur wenn (iff) mindestens ein Pfad von $s$ nach $v$ existiert.

Dies sollte intuitiv klar sein. Für einen Beweis siehe [TR-AI-p2].

Breitensuche und Tiefensuche

Im generischen Graphsuchalgorithmus wird bei jedem Schritt eine Kante zwischen einem bereits erkundeten (explored) Knoten und einem noch nicht-erkundeten (unexplored) Knoten ausgewählt. Die Kanten zwischen den erkundeten und den nicht-erkundeten Kanten werden als Grenz(kanten) (frontier) bezeichnet.

Für die Art und Weise die Grenzkanten für die weitere Erkundung auszuwählen sind die beiden wichtigten Strategien:

  • Breitensuche
  • Tiefensuche

Breitensuche

Schichten (layer) mit Bezug zu $s$

  • Layer 0: nur der Startknoten $s$
  • Layer 1: die direkten Nachbarn von $s$, d.h. die über eine Kante von $s$ direkt erreichbar sind.
  • Layer 2: die Nachbarn von $s$, die von Layer 1 direkt erreichbar sind, aber nicht zu Layer 0 oder 1 gehören.
  • Layer 3: ...

Bei der Breitensuche werden erst alle Knoten einer Schicht vor den Knoten der nächsten Schicht erkundet.

Quiz

Was ist für einen ungerichteten Graphen mit $n \geq 2$ die minimale und maximale Anzahl der Schichten?

  • Minimal $2$
  • Maximal $n$

BFS


Input: Graph $G=(V,E)$ in adjacency-list representation, and a vertex $s \in V$
Postcondition: A vertex is reachable from $s$ if and only if it is marked as "explored".


1  mark $s$ as explored, all other vertices as unexplored
2  $Q :=$ a queue data structure, initialized with $s$
3while $Q$ is not empty do
4   remove (dequeue) the vertex from the front of $Q$, call it $v$
5   for each edge $(v, w)$ in $v$'s adjacency list do
6    if $w$ is unexplored then
7     mark $w$ as explored
8     add $w$ to the end of $Q$ (enqueue)

Korrektheit und Laufzeit von BFS

Theorem 8.2 (Eigenschaften von BFS): Für jeden ungerichteten oder gerichteten Graphen $G=(V, E)$ in Adjazenzlistenrepräsentation und für jeden Startknoten $s \in V$ gilt:

  1. Nach Beendigung von BFS ist ein Knoten $v \in V$ genau dann als erkundet markiert, wenn und nur wenn (iff) es mindestens einen Pfad von $s$ nach $v$ gibt.

  2. Die Laufzeit von BFS ist $O(m+n)$.

  3. Die Laufzeit der Zeilen 2-8 des Pseudocodes ist $O(m_s+n_s)$ mit $n_s$ bzw. $m_s$ der Anzahl der Knoten bzw. Kanten, die von $S$ erreichbar sind.

  • Zeile 1 ist $O(n)$: Initialisierung einer Liste/Array mit Länge $n$
  • Zeile 2 ist $=O(1)$
  • Loop in Zeile 3 geht über $n_S$ Knoten, d.h. 3 und 4 ist $O(n_S)$
  • Loop in Zeile 5 (und somit auch Zeile 6) geht über $2*m_S$ (ungerichteter Graph) bzw. $m_S$ (gerichteter Graph) Kanten unabhängig vom äußeren Loop.
  • 7 und 8 wird $n_S$-mal ausgeführt.

Daraus folgt direkt 3. des Theorems 8.2 und somit auch 2. des Theorems.
Punkt 1. des Theorems folgt aus der Korrektheit von GenericSearch.

Kürzeste Wege

Problem Definition

Input: Ein ungerichteter oder ein gerichteter Graph $G=(V,E)$ und ein Startknoten $s \in V$
Output: dist$(s,v)$ für jeden Knoten $v \in G$

dist$(s,v)$ ist (hier) die minimale Anzahl der Schritte (Kanten), die man von $s$ zu $v$ benötigt.

Ein Algorithmus zur Berechnung der kürzesten Wege kann mit BFS direkt abgeleitet werden, wenn man bedenkt, dass der Schichtindex der gewünschten Information entspricht.

Augmented BFS


Input: Graph $G=(V,E)$ in adjacency-list representation, and a vertex $s \in V$
Postcondition: for every vertex $v \in V$, the value $l(v)$ equals to the true shortest path dist$(s,v)$


mark $s$ as explored, all other vertices as unexplored
$l(s) := 0, l(v):= \infty$ for every $v\neq s$
$Q :=$ a queue data structure, initialized with $s$
while $Q$ is not empty do
 remove the vertex from the front of Q, call it $v$
for each edge $(v, w)$ in $v$'s adjacency list do
  if $w$ is unexplored then
   mark $w$ as explored
   $l(w) := l(v)+1$
   add $w$ to the end of $Q$

Berechnung der verbundenen Komponenten (connected components)

Definition: Verbundene Komponenten eines ungerichteten Graphen

Eine verbundene Komponente ist eine Untermenge $S$ der Knoten von $V$, d.h. $S \subseteq V$, mit der Eigenschaft, dass von jedem Knoten in $S$ ein Weg (path) zu jedem anderen Knoten in $S$ existiert.

Graphensuche kann zur Identifikation der verbundenen Komponenten verwendet werden. Es muss lediglich eine äußere Schleife hinzugefügt werden, um über alle Knoten zu iterieren. Dann hat man für jede verbundene Komponente einen Startknoten.

Problem Definition UCC (undirected connected components)

Input: Ein ungerichteter Graph $G=(V,E)$ und ein Startknoten $s \in V$
Ziel: Identifiziere die verbundenen Komponenten von $G$

Quiz

Was ist die minimale und maximale Anzahl der verbundenen Komponenten, die ein Graph mit $n$ Knoten und $m$ Kanten haben kann.

Lösung:

  • minimal: 1
  • maximal: n
Anwendungen
  • Erkennen von Netzwerkfehlern
  • Datenvisualisierung
  • Clustering

UCC


Input: undirected Graph $G=(V,E)$ in adjacency-list representation with $V = \{1,2,3,\dots, n \}$
Postcondition: for every vertex $v, u \in V$, $cc(u)=cc(v)$ if and only if $u$ and $c$ are in the same connected component.


mark all vertices as unexplored
$numCC := 0$
for $i := 1$ to $n$ do \\ try all vertices
if $i$ is unexplored then \\ avoid redundancy
  $numCC := numCC + 1$ \\ new component
  \\ call BFS starting at i
  $Q :=$ a queue data structure, initialized with $i$
  while $Q$ is not empty do
   remove the vertex from the front of $Q$, call it $v$
   $cc(v) := numCC$
   for each edge $(v, w)$ in $v$'s adjacency list do
    if $w$ is unexplored then
     mark $w$ as explored
     add $w$ to the end of $Q$

Tiefensuche (depth-first search)


DFS (Iterative Implementation)


Input: Graph $G=(V,E)$ in adjacency-list representation, and a vertex $s \in V$
Postcondition: a vertex is reachable from $s$ if and only if it is marked as "explored"


mark all vertices as unexplored
$S := a$ stack data structure, initialized with $s$
while $S$ is not empty do
 remove ("pop") the vertex $v$ from the front of $S$
if $v$ is unexplored then
  mark $v$ as explored
  for each edge $(v, w)$ in $v$'s adjacency list do
   add ("push") $w$ to the end of $S$


DFS (Rekursive Implementation)


Input: Graph $G=(V,E)$ in adjacency-list representation, and a vertex $s \in V$
Postcondition: a vertex is reachable from $s$ if and only if it is marked as explored


// all vertices unexplored before outer call
mark $s$ as explored
for each edge $(s,v)$ in $s$'s adjacency list do
if $v$ is unexplored then
  DFS$(G,v)$

Korrektheit und Laufzeit von DFS

Theorem (Eigenschaften von DFS): Für jeden ungerichteten oder gerichteten Graphen $G=(V, E)$ in Adjazenzlistenrepräsentation und für jeden Startknoten $s \in V$ gilt:

  1. Nach Beendigung von DFS ist ein Knoten $v \in V$ genau dann als erkundet markiert, wenn und nur wenn (iff) es einen Pfad von $s$ nach $v$ gibt.

  2. Die Laufzeit von DFS ist $O(m+n)$ mit $m=|E|$ und $n=|V|$.

  1. Gilt wegen der Korrektheit des generischen Graphsuchalgorithmus (siehe oben)
  2. In DFS wird jede Kante maximal zweimal (zweimal bei ungerichteten Graphen) "durchlaufen". Stack-push/pop ist in $O(1) \Rightarrow O(m)$. Initialisierung aller Knoten als unexplored ist $O(n)$.

D.h. es ist auch (wie bei BFS) $O(m_s+n_s)$ möglich, wenn nur explored gekennzeichnet wird.

  • Gleiche Laufzeit wie BFS.
  • Beispielsweise kann im UCC-Algorithmus Breitensuche durch Tiefensuche ersetzt werden.

Topologische Ordnung

Definition

Für einen gerichteten Graphen $G=(V,E)$ ist eine topologische Ordnung eine Zuordnung $f(v)$ für jeden Knoten $v \in V$ zu einer unterschiedlichen Zahl, sodass folgende Eigenschaft gilt:

$$ \forall (v,w) \in E: f(v) < f(w) $$

Die Funktion $f(.)$ ordnet somit die Knoten in eine Reihenfolge. Alle Knoten von $G$ werden durch die Kanten so verbunden, dass eine Kante immer von einem Knoten mit niedriger Ordnung zu einem Knoten mit höherer Ordnung zeigt.

Anwendungen

Reihenfolgebedingungen (precedence constraints) wie z.B.

Abhängigkeiten (Erfüllungen von Voraussetzungen)

  • von Aufgaben
  • von Kursen
  • zwischen Softwarepaketen geben die Installationsreihenfolge (z.B. für einen Paketmanager) vor.

Quiz

Gegeben sei $G = (V,E)$ mit

  • $V = \{a,b,c,d\}$
  • $E = \{(a,b), (a,c), (b,d), (c,d) \}$

Wieviele unterschiedliche topologische Ordnungen gibt es für $G$?

Antwort: 2

DAG

  • Ein DAG ist ein Graph ohne einen Zyklus.
  • Ein Zyklus ist ein Weg/Pfad von $v$ zu $v$ über andere Knoten als $v$.

Theorem 8.6

Jeder DAG hat eine topologische Ordnung.

Lemma 8.7

Jeder DAG hat mindestens einen Quellknoten (source).

Ein Quellknoten ist ein Knoten ohne eingehende Kanten.

Beweisidee von 8.7: Folgt man ausgehend von jedem beliebigen Knoten Wege rückwärts (vom Kopf zum Schwanz der Kanten), stößt man auf einen Quellknoten. Sonst würde man einen Zyklus erkennen, d.h. der Graph wäre kein DAG.

Beweis von 8.6:

  1. Wähle einen beliebigen Quellknoten $v_1$ aus und setze $f(v_1)=1$
  2. Reduziere den Graphen $G$ um $v_1$ (und um die ausgehenden Kanten von $v_1$). Dies ergibt $G'$.
  3. Wähle wieder einen beliebigen Quellknoten $v_2$ des reduzierten Graphen aus (analog 1.) und setze $f(v_2)=2$
  4. Reduziere den Graphen $G'$ um $v_2$ (und um die ausgehenden Kanten von $v_2$). Dies ergibt $G''$.
  5. usw. bis der Graph leer ist.

Dieses Vorgehen erzeugt eine korrekte topologische Ordnung, da der Graph immer nur um Knoten ohne eingehende Kanten reduziert wird.
Bei der Reduktion eines DAG $G$ zu einem neuen Graphen $G'$ ist $G'$ auch immer ein DAG, da dabei keine Zyklen hinzugefügt werden (können).

Problemdefinition: Topologische Sortierung / Berechung einer topologischen Ordnung

Input: Ein gerichteter Graph $G=(V,E)$.
Output: Eine topologische Ordnung der Knoten von $G$.

Ein Algorithmus kann nach dem Vorgehen in den Beweisen von 8.6 und 8.7 entwickelt werden. Wenn in jeder Iteration/Rekursion eine $O(n)$ Subroutine für das Finden eines Quellknotens verwendet wird, hat dies Zeitkomplexität $O(n^2)$. Bei Nutzung einer Queue erhält man Kahns-Algorithmus. Den wir hier nicht weiter betrachten.

Für dichte Graphen mit $m\in\Theta(n^2)$ ist ein solcher Algorithmus linear in der Anzahl der Kanten $m$. Für dünnbesetzte Graphen gilt dies nicht, da bei diesen $n^2$ viel größer als $m$ ist.

Topologische Sortierung mit DFS

Mit DFS kann eine topologische Ordnung für DAGs erzeugt werden. Dabei wird mit einer äußeren Schleife (in der Subroutine TopoSort) über alle Knoten sichergestellt, dass alle Knoten einen $f$-Wert erhalten.


TopoSort


Input: directed acyclic graph $G=(V,E)$ in adjacency-list representation
Postcondition: the $f$-values of vertices constitute a topological ordering of $G$



mark all vertices as unexplored
$curLabel$ := $|V|$
for each vertex $v \in V$ do
if $v$ is unexplored then
  DFS-Topo$(G,v)$


DFS-Topo (Rekursive Implementation)


Input: Graph $G=(V,E)$ in adjacency-list representation, and a vertex $s \in V$
Postcondition: every vertex $v$ reachable from $s$ is marked as "explored" and has an assigned $f$-value


mark $s$ as explored
for each edge $(s,v)$ in $s$'s adjacency list do
if $v$ is unexplored then
  DFS-Topo$(G,v)$
elif
   Cycle-Error Handling
$f(s)$ := $curLabel$
$curLabel := curLabel - 1$

Quiz

Was passiert, wenn der Algorithmus DFS-Topo auf einen gerichteten Graphen mit Zyklen angewandt wird?

  1. Der Algorithmus stoppt oder stoppt nicht.
  2. Der Algorithmus stoppt nicht (unendlicher Loop).
  3. Der Algorithmus hält immer und berechnet manchmal eine korrekte topologische Ordnung. Manchmal aber auch eine falsche Ordnung.
  4. Der Algorithmus hält immer und berechnet nie eine korrekte topologische Ordnung.

Antwort: 4.

Theorem 8.8.

Für jeden azyklischen Graphen $G =(V,E)$

  1. Nach Abschluss von DFS-Topo gibt eine korrekte topologische Ordnung für die $f$-Werte aller Knoten.
  2. Die Laufzeit von DFS-Topo ist $O(n+m)$ mit $m=|E|$ und $n=|V|$

zu 2.

Der Algorithmus durchläuft jede Kante nur einmal (for each edge in DFS-Topo) bzw. jeden Knoten (for each vertex $v \in V$ in TopoSort) unäbhängig voneinander. Das impliziert eine Gesamtlaufzeit von $O(n+m)$

zu 1. Im Algorithmus folgen die Zeilen
$f(s)$ := $curLabel$
$curLabel := curLabel - 1$
aufeinander, daher haben alle Knoten unterschiediche Labels/$f$-Werte.

Für zwei Knoten $v$ und $w$, die mit einer Kante $(v,w)$ verbunden sind, muss gelten $f(v)<f(w)$.

Zwei Fälle:

  1. $v$ wird vor $w$ durch den Algorithmus erkundet. Später wird $w$ durch einen rekursiven Aufruf von $v$ aus erkundet. Durch die last-in first-out Natur des rekursiven Aufrufes wird die Zuweisung des f-Wertes zuerst bei $w$ vorgenommen. So erhält $w$ einen höheren $f$-Wert, wie in der topologischen Ordnung gefordert.
  2. $w$ wird vor $v$ erkundet. Durch weiteres Explorieren durch rekursive Aufrufe von $w$ aus kann $v$ nicht erreicht werden, da es sonst einen Pfad von $w$ zu $v$ und somit einen Zyklus gäbe. D.h. die Rekursion von $w$ aus muss erst abgeschlossen sein, bevor $v$ erkundet wird. Das bedeutet auch $f(v)<f(w)$ wie gewünscht.

Literatur

  • [TR-AI-p2] Tim Roughgarden: Algorithms Illuminated, Part 2, Graph Algorithms and Data Structures