Bäume und Graphen
Ein Navigationsgerät kennt Kreuzungen und Straßen mit Fahrzeiten und findet die schnellste Route. Dein Dateisystem ordnet Dateien in Ordnern, die wieder in Ordnern liegen. Beides sind Netze aus Punkten und Verbindungen, das erste mit Kreisen und Alternativen, das zweite ohne.
Du lernst, wie man solche Netze als Bäume und Graphen speichert und durchläuft, wie man kürzeste Wege und billigste Verbindungsnetze findet und wie Rätsel als Suche in einem Graphen auftreten.
Binärbaum und geordneter Binärbaum
Ein Binärbaum besteht aus Knoten. Der oberste ist die Wurzel. Jeder Knoten hat höchstens zwei Kinder, ein linkes und ein rechtes. Knoten ohne Kinder sind Blätter. Jeder Knoten ist die Wurzel eines Teilbaums. Die Höhe ist die Zahl der Kanten auf dem längsten Weg von der Wurzel zu einem Blatt.
Ein geordneter Binärbaum (binärer Suchbaum) hat in jedem Knoten nur kleinere Werte im linken und nur größere im rechten Teilbaum. Suchen und Einfügen gehen links oder rechts weiter, höchstens so oft, wie die Höhe angibt: Schritte bei einem ausgeglichenen Baum mit Knoten, bei einem entarteten, der zur Liste wird (etwa nach dem Einfügen einer sortierten Folge).
Der Baum, der beim Einfügen von 8, 3, 10, 1, 6, 14, 4 entsteht:
| Knoten | linkes Kind | rechtes Kind |
|---|---|---|
| 8 | 3 | 10 |
| 3 | 1 | 6 |
| 10 | keins | 14 |
| 6 | 4 | keins |
| 1, 4 und 14 | keins | keins |
Traversierung: Preorder, Inorder, Postorder
Die Traversierung besucht jeden Knoten genau einmal. Die drei Tiefenreihenfolgen unterscheiden sich darin, wann die Wurzel eines Teilbaums an der Reihe ist. Die Teilbäume durchläuft man nach derselben Regel, also rekursiv.
- Preorder: Wurzel, linker Teilbaum, rechter Teilbaum.
- Inorder: linker Teilbaum, Wurzel, rechter Teilbaum.
- Postorder: linker Teilbaum, rechter Teilbaum, Wurzel.
Als Funktion: tut nichts, wenn k gleich ist. Sonst ruft es auf, gibt aus und ruft auf.
Drei Durchläufe
Drei Durchläufe
Welche Folgen liefern Preorder, Inorder und Postorder für den Baum oben?
Preorder: Wurzel 8, dann der linke Teilbaum (3, 1, dann 6 mit 4), dann der rechte (10, 14). Folge: 8, 3, 1, 6, 4, 10, 14.
Inorder: Der linke Teilbaum von 8 liefert 1, 3, 4, 6, dann kommt 8, dann 10, 14. Folge: 1, 3, 4, 6, 8, 10, 14. Sie ist aufsteigend sortiert, bei jedem geordneten Binärbaum.
Postorder: Jede Wurzel kommt nach ihren Teilbäumen. Folge: 1, 4, 6, 3, 14, 10, 8.
Graphen, Adjazenzmatrix und Adjazenzliste
Ein Graph besteht aus Knoten und Kanten, die je zwei Knoten verbinden. Kanten eines gerichteten Graphen haben eine Richtung (Einbahnstraßen), die eines ungerichteten nicht. Bei einem gewichteten Graphen trägt jede Kante eine Zahl, etwa die Fahrzeit. Ein Weg ist eine Folge von Knoten, bei der aufeinanderfolgende durch Kanten verbunden sind, seine Länge ist die Summe der Gewichte. Ein Zyklus ist ein Weg, der am Startknoten endet und keine Kante zweimal benutzt. Ein Baum ist ein zusammenhängender ungerichteter Graph ohne Zyklus, mit Knoten und Kanten.
Die Adjazenzmatrix hat je eine Zeile und Spalte pro Knoten. In Zeile A und Spalte B steht das Gewicht der Kante von A nach B oder ein Zeichen für „keine Kante“. Sie braucht Zellen, und ob eine Kante existiert, sieht man in . Die Adjazenzliste speichert für jeden Knoten seine Nachbarn mit den Gewichten. Mit Kanten braucht sie Platz und lohnt sich bei wenigen Kanten.
Im Stadtplan sind die Knoten Kreuzungen, die Gewichte Fahrzeiten in Minuten. Kanten: A-B 4, A-C 2, B-D 5, B-E 10, C-D 8, C-F 9, D-E 3, D-F 6, E-G 4, F-G 2, F-H 3, G-H 7.
Breitensuche und Tiefensuche
Beide Verfahren besuchen alle Knoten, die vom Startknoten aus erreichbar sind, und markieren besuchte Knoten, damit ein Zyklus keine Endlosschleife erzeugt. Die Breitensuche besucht erst alle Nachbarn des Startknotens, dann deren Nachbarn und so weiter. Sie merkt sich offene Knoten in einer Warteschlange und findet Wege mit den wenigsten Kanten. Die Tiefensuche geht immer zu einem unbesuchten Nachbarn, so weit es geht, und kehrt dann zurück (Backtracking). Sie nutzt einen Stapel oder Rekursion. Beide brauchen mit einer Adjazenzliste Schritte.
Im Stadtplan ab A, Nachbarn alphabetisch, besucht die Breitensuche A, B, C, D, E, F, G, H und die Tiefensuche A, B, D, C, F, G, E, H.
Kürzeste Wege: Dijkstra und Bellman-Ford
Gesucht ist der kürzeste Weg von einem Startknoten zu den anderen Knoten. Dijkstra setzt Gewichte von mindestens 0 voraus. Jeder Knoten hat eine vorläufige Entfernung (am Start 0, sonst unendlich). Dijkstra schließt wiederholt den offenen Knoten mit kleinstem ab: Seine Entfernung ist nun endgültig. Dann prüft er jede Kante von zu einem Nachbarn mit Gewicht und setzt auf , wenn das kleiner ist, und merkt sich als Vorgänger. Die Wahl des kleinsten ist richtig, weil bei Gewichten von mindestens 0 kein späterer Umweg einen abgeschlossenen Knoten verbessert. Mit einer Datenstruktur, die den kleinsten Wert schnell findet, braucht Dijkstra Schritte.
Bellman-Ford prüft in Runden jede Kante auf dieselbe Weise. Es erlaubt negative Gewichte und erkennt einen negativen Zyklus: Verbessert sich in Runde noch ein Wert, gibt es einen. Es braucht Schritte.
Dijkstra im Stadtplan
Dijkstra im Stadtplan
Bestimme mit Dijkstra die kürzesten Fahrzeiten von A zu allen Knoten des Stadtplans und den Weg von A nach H.
Die Tabelle zeigt die Entfernungen nach dem Abschluss des genannten Knotens (∞ heißt noch unbekannt).
| abgeschlossen | A | B | C | D | E | F | G | H |
|---|---|---|---|---|---|---|---|---|
| Start | 0 | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ |
| A | 0 | 4 | 2 | ∞ | ∞ | ∞ | ∞ | ∞ |
| C | 0 | 4 | 2 | 10 | ∞ | 11 | ∞ | ∞ |
| B | 0 | 4 | 2 | 9 | 14 | 11 | ∞ | ∞ |
| D | 0 | 4 | 2 | 9 | 12 | 11 | ∞ | ∞ |
| F | 0 | 4 | 2 | 9 | 12 | 11 | 13 | 14 |
D wird erst über C mit 2 + 8 = 10 erreicht, nach B über B mit 4 + 5 = 9. Für F lohnt der Weg über D nicht (9 + 6 = 15 ist mehr als 11). Danach ändert sich nichts mehr. Die Fahrzeit nach H ist 14. Über die Vorgänger rückwärts: H von F, F von C, C von A. Der Weg ist A, C, F, H mit 2 + 9 + 3 = 14.
Minimale Spannbäume: Prim und Kruskal
Ein Spannbaum eines zusammenhängenden ungerichteten Graphen ist ein Baum, der alle Knoten verbindet, er hat Kanten. Ein minimaler Spannbaum hat die kleinste Summe der Gewichte, wie das billigste Leitungsnetz, das alle Orte verbindet. Prim beginnt an einem Knoten und fügt immer die billigste Kante hinzu, die einen neuen Knoten an den Baum anschließt. Kruskal geht alle Kanten nach aufsteigendem Gewicht durch und nimmt jede, die keinen Zyklus schließt. Beide wählen in jedem Schritt lokal das Billigste (Greedy, siehe unten).
Im Stadtplan wählen beide A-C (2), F-G (2), D-E (3), F-H (3), A-B (4), E-G (4) und B-D (5). Das Gesamtgewicht ist 2 + 2 + 3 + 3 + 4 + 4 + 5 = 23.
Greedy-Verfahren, Suchraum und informierte Suche
Ein Greedy-Verfahren trifft in jedem Schritt die lokal beste Wahl und nimmt sie nie zurück. Das führt nicht immer zum Optimum. Wechselgeld für 6 mit Münzen 1, 3 und 4: Greedy nimmt 4, 1, 1 (drei Münzen), optimal sind 3 und 3. Bei Dijkstra, Prim und Kruskal führt die lokale Wahl nachweislich zum Optimum.
Viele Aufgaben sind eine Suche in einem Suchraum, der selbst ein Graph ist: Knoten sind Zustände (eine Stellung beim Schiebepuzzle, ein Ort auf der Karte), Kanten sind erlaubte Züge. Gesucht ist ein Weg vom Start- zu einem Zielzustand. Breiten- und Tiefensuche wissen nichts über das Ziel, und der Raum wächst oft sehr schnell.
Der A*-Algorithmus ist eine informierte Suche. Er nutzt eine Heuristik , eine Schätzung der Restkosten von zum Ziel, etwa die Luftlinie. Wie Dijkstra wählt er den Knoten mit kleinstem , wobei die bisherigen Kosten vom Start sind. Ist nie größer als die wahren Restkosten, findet A* einen kürzesten Weg.
Dijkstra mit negativen Gewichten
Dijkstra mit negativen Gewichten
„Dijkstra findet auch bei negativen Kantengewichten den kürzesten Weg.“
Dann schließt er Knoten zu früh ab. Gerichtete Kanten: A nach B mit 2, A nach C mit 3, C nach B mit -2. Dijkstra schließt B mit 2 ab, obwohl A, C, B nur 3 - 2 = 1 lang ist. Dafür nimmt man Bellman-Ford.
Breitensuche findet den schnellsten Weg
Breitensuche findet den schnellsten Weg
„Die Breitensuche findet im Stadtplan immer den schnellsten Weg.“
Sie findet den Weg mit den wenigsten Kanten. Von A nach E hat A, B, E zwei Kanten und 4 + 10 = 14 Minuten, der schnellste Weg A, B, D, E hat drei Kanten und 4 + 5 + 3 = 12 Minuten.
Zusammenfassung
Im geordneten Binärbaum steht links Kleineres, rechts Größeres. Inorder liefert die sortierte Folge, Preorder und Postorder unterscheiden sich in der Stellung der Wurzel.
Graphen können gerichtet und gewichtet sein. Die Adjazenzmatrix braucht Zellen, die Adjazenzliste .
Die Breitensuche nutzt eine Warteschlange und findet Wege mit den wenigsten Kanten, die Tiefensuche Stapel oder Rekursion.
Dijkstra findet kürzeste Wege bei Gewichten von mindestens 0, Bellman-Ford auch bei negativen und erkennt negative Zyklen.
Prim und Kruskal bauen einen minimalen Spannbaum auf. Greedy-Verfahren führen nicht immer zum Optimum.
Ein Suchraum ist ein Graph aus Zuständen und Zügen. A* nutzt eine Heuristik, die die Restkosten nie überschätzt.
Übungen
Die Zahlen 6, 2, 9, 1, 4, 7 werden in dieser Reihenfolge in einen anfangs leeren geordneten Binärbaum eingefügt. Welche Zahl steht in der Postorder-Folge an vierter Stelle?
Ein Graph hat 1000 Knoten und nur 3000 Kanten. Welche Darstellung braucht deutlich weniger Speicher?
Ein ungerichteter Graph hat die Knoten S, A, B und T und die Kanten S-A (2), S-B (5), A-B (1), A-T (6) und B-T (2). Wie lang ist der kürzeste Weg von S nach T?
Ein ungerichteter Graph hat die Knoten P, Q, R und S und die Kanten P-Q (3), P-R (5), Q-R (1), Q-S (6) und R-S (4). Welches Gesamtgewicht hat ein minimaler Spannbaum?
Du suchst mit A* die kürzeste Fahrstrecke in Kilometern zwischen zwei Orten und nimmst die Luftlinie zum Ziel als Heuristik. Warum findet A* damit einen kürzesten Weg?