Was bisher geschah ADT Menge mit Operationen: I Suche nach einem Element I Einfügen eines Elementes I Löschen eines Elementes Realisierung durch verschiedene Datenstrukturen: I lineare Datenstrukturen: Array, Liste, Hashtabelle I hierarchische Datenstrukturen: Suchbäume, balancierte Suchbäume Datenstrukturen mit Zugriff nur auf ein spezielles Element: Stack, Queue, Priority-Queue mit 1. Ansehen (top, first, getmin) 2. Entfernen (pop, dequeue, deletemin) 3. Einfügen einen Elementes (push, enqueue, add) rekursive Baumdurchquerungen: pre-, post-, inorder 187 Nichtrekursive Baumdurchquerungen (für Bäume beliebiger Knotengrade) Zwischenspeicher notwendig zur Verwaltung der Menge La der teilweise abgearbeiteten Knoten Allgemeiner Durchquerungs-Algorithmus: 1. La = {s} (Wurzel s des zu durchquerenden Baumes) 2. solange La 6= ∅ wiederhole: Auswahl und Entfernen eines Knotens u aus La (u wird „verlassen“) Einfügen aller Kinder von u in La (Kinder von u werden „entdeckt“) 188 Nichtrekursive Baumdurchquerungen Datenstruktur für Zwischenspeicher La (entdeckte, aber noch nicht verlassene Knoten) bestimmt Durchquerungsreihenfolge. Tiefensuche Verwaltung von La als Stack Einfügen der Nachbarn: push I Auswahl des Knotens, der zuletzt in La eingefügt wurde (pop) (gleiche Reihenfolge wie preorder-Durchquerung) I I Breitensuche Verwaltung von La als Queue Einfügen der Nachbarn: enqueue I Auswahl des Knotens, der zuerst in La eingefügt wurde (dequeue) (level-order-Durchquerung) I I 189 Durchquerung von Bäumen mit Priority-Queue allgemeiner Durchquerungs-Algorithmus: 1. La = {s} (Wurzel s des zu durchquerenden Baumes) 2. solange La 6= ∅ wiederhole: Auswahl und Entfernen eines Knotens u aus La Einfügen aller Kinder von u in La Verwaltung von La als Stack: Tiefensuche Verwaltung von La als Queue: Breitensuche Verwaltung von La als Priority-Queue: Auswahl des Knotens aus La : getmin (deletemin) Einfügen der Kinder in La : add (mit geeigneten Prioritäten) häufige Anwendung: Priorität = Bewertung der Knoten (Heuristik) heuristische Suchverfahren, z.B. in Spielbäumen, Labyrinth-Suche (mehr dazu in LV Wissensverarbeitung) 190 Wiederholung Graph gerichteter Graph G = (V , E) mit E ⊆ {(u, v ) | u, v ∈ V } ungerichteter Graph G = (V , E) mit E ⊆ {{u, v } | u, v ∈ V ∧ u 6= v } Graph mit Knotenmarkierung in Menge CV (gerichtet oder ungerichtet) G = (V , E, lV ) mit lV : V → CV Graph mit Kantenmarkierung in Menge CE (gerichtet oder ungerichtet) G = (V , E, lE ) mit lE : E → CE (Gewichte) Repräsentation als I Diagramm (i.A. zur maschinellen Verarbeitung untauglich) I (Adjazenz-)Matrix (sinnvoll für dichte Graphen) explizite Darstellung der Relation: I I I Liste von Kanten Adjazenzliste (Knoten → Liste aller Nachfolger) 191 Teilgraphen Teilgraph in G = (V , E): Graph G0 = (V 0 , E 0 ) mit V 0 ⊆ V und E 0 ⊆ E induzierter Teilgraph einer Knotenmenge V 0 ⊆ V in G = (V , E): Graph G0 = (V 0 , E 0 ) mit E 0 = E ∩ (V 0 )2 Weg in G = (V , E): Folge (p1 , . . . , pn ) ∈ V n mit ∀i ∈ {1, . . . , n − 1} : (pi , pi+1 ) ∈ E Weg von u nach v in G: Weg (p1 , . . . , pn ) in G mit p1 = u und pn = v Pfad in G = (V , E): (gerichteter) Teilgraph P = (V 0 , E 0 ) in G mit V 0 = {p1 , . . . , pn }, E 0 = {(pi , pi+1 ) | i ∈ {1, . . . , n − 1}} (Pfad von p1 nach pn ) Kreis in G = (V , E): (gerichteter) Teilgraph C = (V 0 , E 0 ) in G mit V 0 = {p1 , . . . , pn } und E 0 = {(pi , pi+1 ) | i ∈ {1, . . . , n − 1}} ∪ {(pn , p1 )} 192 Zusammenhangskomponenten eines Graphen (gerichteter oder ungerichteter) Graph G heißt zusammenhängend gdw. ∀(u, v ) ∈ V 2 existiert ein Pfad zwischen u und v Zusammenhangskomponente eines ungerichteten Graphen G = (V , E): ⊆-maximale Knotenmenge V 0 ⊆ V , so dass der von V 0 induzierte Teilgraph von G zusammenhängend ist 193 Graphdurchquerungen – Spezifikation Ziel: Besuch aller Knoten des Graphen G = (V , E) Spezifikation Graphdurchquerung: V: Eingabe Graph G = (V , E) (und evtl. Startknoten s ∈ V ) N: Ausgabe Folge (x1 , . . . , xn ) (lineare DS) mit 1. {x1 , . . . , xn } = V Jeder Knoten wird (wenigstens einmal) besucht. 2. ∀i, j ∈ {1, . . . , n} : i 6= j → xi 6= xj Kein Knoten wird mehrmals besucht. Spezialfall Bäume: In den Baumdurchquerungsalgorithmen garantieren die Baumeigenschaften die Erfüllung der Nachbedingungen: Bäume sind I zusammenhängend (garantiert Nachbedingung 1.) I kreisfrei (garantiert Nachbedingung 2.) 194 Hilfsmengen für Graphdurchquerungen Idee: Verallgemeinerung des allgemeinen (nichtrekursiven) Baum- zu einem Graphdurchquerungsalgorithmus Erweiterungen zur Garantie der Nachbedingungen notwendig: Hilfsmenge Li zur Verwaltung der noch nicht entdeckten Knoten des Graphen zu Beginn Li = V , am Ende Li = ∅ (für Nachbed. 2.: kein Knoten mehrmals) Neustart (für unzusammenhängende Graphen nötig) falls La = ∅, aber noch nicht alle Knoten besucht, Durchquerung bei beliebigem noch nicht entdeckten Knoten neu beginnen (für Nachbed. 1.: jeder Knoten wenigstens einmal) Menge der Nachbarn eines Knotens u ∈ V in einem Graphen G = (V , E): N(u) = {v ∈ V | (u, v ) ∈ E} 195 Graph-Durchquerungs-Algorithmus Erweiterung des (allgemeinen) Baum-Durchquerungs-Algorithmus: Algorithmus : Graph-Durchquerung Eingabe : G = (V , E) (und evtl. Startknoten s ∈ V ) Ausgabe : x = (x1 , . . . , xn ) x ← (); La ← {s}; Li ← V \ {s} (s Startknoten oder beliebig) solange La 6= ∅ ∨ Li 6= ∅ : Auswahl und Entfernen eines Knotens u aus La und Anhängen an (das Ende von) x La ← La \ {u}; x ← x ◦ (u) Einfügen aller Nachbarn von u, die noch nicht entdeckt worden (also noch in Li ) sind, in La : La ← (N(u) ∩ Li ); Li ← Li \ N(u) Neustart (nur bei unzusammenhängenden Graphen nötig) Falls La = ∅ und Li 6= ∅: Auswahl eines (beliebigen) Knoten v ∈ Li und La ← {v }; Li ← Li \ {v } 196 Graph-Durchquerung Invariante des Graph-Durchquerungs-Algorithmus: Mengen La , Li , {xi | xi ∈ x} sind paarweise disjunkt (Warum?) Warum terminiert der Graph-Durchquerungs- Algorithmus? häufig anzutreffende Variante: Veranschaulichung durch verschiedene Färbung der Knoten in Li , La und Menge der „erledigten“ Knoten und schrittweise Umfärbung (oft auch Verwaltung der Umfärbungs-Zeitpunkte) 197 Datenstrukturen für Hilfmengen auf Li benötigte Operationen: Finden, Einfügen, Entfernen von Knoten (beliebige Realisierung des ADT Menge) auf La benötigte Operationen: Auswahl (und Entfernen) des „nächsten“ Knotens, Einfügen von Knoten (Nachbarn) (Realisierung z.B. durch Stack, Queue, Priority-Queue) Spezielle Graph-Durchquerungen analog Baumdurchquerungen durch Datenstruktur zur Verwaltung von La : Tiefensuche (DFS) bei Verwaltung von La als Stack Breitensuche (BFS) bei Verwaltung von La als Queue Beispiele (Tafel) 198 Graphen mit Kantenmarkierungen Markierung der Kanten eines Graphen G = (V , E): I Menge L von Kantenmarkierungen (häufig , , I Markierungsfunktion l : E → L Anwendungsbeispiele: I Länge der Kante (z.B. Navigationssystem) I Qualität der Kante (z.B. Autobahn, Feldweg) I Preis der Kante (z.B. Fahrtkosten) I Fassungsvermögen (Flussgraphen) I (zeitweises) Sperren der Kanten numerische Markierungen heißen oft Gewichte N Z R≥0, R) L-gewichteter Graph G = (V , E, l): Graph (V , E) mit Markierungsfunktion l : E → L (Gewichte aus L) gewichteter Graph G = (V , E, l): Graph (V , E) mit Markierungsfunktion l : E → L (Gewichte aus beliebiger Menge L) 199 Kürzeste Wege in R≥0-gewichteten Graphen Aufgabe: Bestimmung kürzester Pfade (und ihrer Länge) von einem Knoten s eines ≥0 -gewichteten Graphen (positive Kantengewichte) zu allen von s erreichbaren Knoten R V: gewichteter Graph G = (V , E, l) mit l : V → Startknoten s ∈ V R≥0, N: Ausgabe S = {(v , lv ) | v ∈ V } mit ∀v ∈ V : falls v von s erreichbar, ist (v , lv ) ∈ S und lv das Minimum über die Länge (Summe der Markierungen) aller Pfade von s nach v (und evtl. ein kürzester Pfad von s nach v als Knotenfolge) 200 Algorithmus von Dijkstra (Dijkstra, 1959) Ziel: Bestimmung kürzester Pfade von einem Knoten s eines ≥0 -gewichteten Graphen zu allen von s erreichbaren Knoten Idee: Graph-Durchquerungs-Algorithmus mit R I Verwaltung von La als Priority-Queue I Priorität eines Knotens u: Länge des kürzesten bisher bekannten Pfades von s nach u (Summe des kürzesten bisher bekannten Weges von s zum Vorgänger v von u und der Kante (v , u) I falls zu einem Knoten (u, p) ∈ La ein Pfad mit Kosten p0 < p gefunden wird, Änderung (update) der Priorität von u: La ← (La \ {(u, p)}) ∪ {(u, p0 )} I (evtl. Verwaltung des zu jedem Knoten ausgewählten Pfades) Achtung: nicht anwendbar bei negativen Gewichten (dann stattdessen Algorithmus von Bellman und Ford) 201