Interaktives Graphentheorie-Lernen
Interaktives Graphentheorie-Lernen
Guest User
Using app without sign in
Interaktiver A*-Wegfindungs-Visualisierer
Findet den kürzesten Weg schneller als Dijkstra, indem eine Heuristik die Suche lenkt
Wählen Sie einen Algorithmus und generieren Sie Schritte, um die Visualisierung zu beginnen
A* findet den günstigsten Weg zwischen zwei Punkten in einem gewichteten Graphen und ist der Algorithmus hinter der Wegfindung in fast allen Spielen und Robotern. Er ist Dijkstra mit einer Ergänzung: einer Schätzung, wie weit jeder Knoten noch vom Ziel entfernt ist. Dadurch drängt die Suche in Richtung Ziel, statt sich gleichmäßig in alle Richtungen auszubreiten. Hart, Nilsson und Raphael veröffentlichten ihn 1968.
Jeder Knoten trägt drei Zahlen: g, die bestätigten Kosten vom Start; h, die geschätzten Restkosten; und f = g + h, die geschätzten Gesamtkosten. A* führt eine offene Menge entdeckter Knoten und dehnt immer denjenigen mit dem kleinsten f aus. Ausdehnen heißt, ihn in die geschlossene Menge zu verschieben und seine Kanten genau wie bei Dijkstra zu relaxieren. Die Suche endet, sobald das Ziel ausgedehnt wird. Mit einem binären Heap läuft das in O((V + E) log V), derselben Schranke wie Dijkstra, berührt aber meist deutlich weniger Knoten.
A* ist der Standard-Wegfinder in Spiel-Engines, Lagerrobotern und Drohnennavigation und treibt Routenplanung an, wo die Luftlinie als untere Schranke verfügbar ist. Er löst außerdem Schiebepuzzles und andere Zustandsraumsuchen mit guter Heuristik. In Interviews folgt er natürlich auf Dijkstra: gefragt wird meist, welche Eigenschaft die Heuristik braucht, damit die Antwort optimal bleibt.
A* ist Dijkstra mit einem zusätzlichen Term. Wo Dijkstra stets den Knoten mit den kleinsten bestätigten Kosten g ausdehnt, nimmt A* den mit dem kleinsten f = g + h, wobei h die noch verbleibenden Kosten schätzt. Setzt man h überall auf null, wird der Pseudocode unten exakt zu Dijkstra.
A*(graph, start, ziel, h):
für jeden Knoten v: g[v] = unendlich
g[start] = 0
f[start] = h(start)
offen = Prioritätswarteschlange mit (f[start], start)
geschlossen = leere Menge
solange offen nicht leer ist:
u = offen.extractMin() // kleinstes f
wenn u == ziel: gib rekonstruiere(u) zurück
füge u zu geschlossen hinzu
für jede Kante (u, v, w):
wenn v in geschlossen: überspringe
versuch = g[u] + w
wenn versuch < g[v]:
vorgänger[v] = u
g[v] = versuch
f[v] = versuch + h(v)
offen.einfügen(f[v], v)
gib kein Pfad zurückDas Überspringen der geschlossenen Menge in Zeile 15 ist nur sicher, wenn h konsistent ist, also h(u) <= w(u, v) + h(v) für jede Kante gilt. Bei einer lediglich zulässigen Heuristik müssen Knoten die geschlossene Menge wieder verlassen dürfen, sonst kann A* einen nicht optimalen Pfad liefern. Der Visualisierer oben skaliert die Luftlinie mit den günstigsten Kosten pro Einheit, die irgendeine Kante bietet. Dadurch wird h über die Dreiecksungleichung konsistent, und ein Wiederöffnen ist nicht nötig.
Fünf Knoten, S im Ursprung und das Ziel G fünf Einheiten rechts davon. Der Kern der Spur ist der Knoten, den A* nie anfasst.
Beispielgraph: S(0,0), A(2,1), B(2,-1), C(1,4), G(5,0). Kanten S-A = 3, S-B = 2, S-C = 4, A-B = 2, A-G = 4, B-G = 6, C-G = 7. Skaliert man die Luftlinie mit den günstigsten Kosten pro Einheit einer Kante (0,894), ergibt sich h(S) = 4,47, h(A) = 2,83, h(B) = 2,83, h(C) = 5,06, h(G) = 0.
A* liefert S nach A nach G zu Kosten von 7 und hat dabei 4 Knoten ausgedehnt. Dijkstra liefert auf demselben Graphen denselben Pfad zu denselben Kosten, dehnt aber 5 aus: er arbeitet C ab, bevor er bereit ist, das Ziel festzuschreiben. C war nie einen Besuch wert, und h ist der Grund, warum A* das wusste, ohne nachzusehen.
Zeit: O((V + E) log V) im schlechtesten Fall mit einem binären Heap · Speicher: O(V)
Der schlechteste Fall ist der von Dijkstra, und zwar aus demselben Grund: jeder Knoten kann einmal in die Prioritätswarteschlange gelangen und jede Kante einmal ein Decrease-Key auslösen, also V Entnahmen und E Aktualisierungen zu je O(log V). Die Heuristik ändert an dieser Schranke nichts. Sie ändert die Konstante: Knoten, deren f die endgültigen Kosten des Ziels übersteigt, werden nie ausgedehnt. Mit h = 0 entartet A* exakt zu Dijkstra; mit perfektem h läuft er direkt den optimalen Pfad entlang. Über 4000 zufällig erzeugte gewichtete Graphen dehnte die obige Implementierung im Mittel 4,58 Knoten aus gegenüber 5,52 bei Dijkstra und lieferte jedes Mal die optimalen Kosten.
A* lohnt seinen Mehraufwand nur, wenn es ein Ziel und eine brauchbare Schätzung der Entfernung dorthin gibt. Fehlt eines von beiden, ist eines der folgenden Verfahren das bessere Werkzeug.
| Alternative | Vorzuziehen, wenn | Kosten |
|---|---|---|
| Dijkstra-Algorithmus | Du brauchst kürzeste Wege zu allen Knoten oder hast keine sinnvolle Heuristik. A* mit h = 0 ist genau das. | O((V + E) log V) |
| Breitensuche | Alle Kanten kosten gleich viel. BFS findet dieselbe Antwort ganz ohne Prioritätswarteschlange. | O(V + E) |
| Bellman-Ford | Einige Kantengewichte sind negativ. A* erbt Dijkstras Annahme nichtnegativer Gewichte und versagt hier. | O(V * E) |
| Bidirektionaler A* | Sehr große Graphen mit einem Start und einem Ziel. Die Suche von beiden Enden halbiert den durchsuchten Bereich grob. | O((V + E) log V) |
| Gewichteter A* (f = g + w*h) | Du tauschst Optimalität gegen Tempo. w > 1 findet Pfade schneller, garantiert die Kosten aber nur bis zum Faktor w. | O((V + E) log V) |
Den ganzen Artikel lesen: A* Search Algorithm: Step-by-Step Guide
Den ganzen Artikel lesen: A* Search Algorithm in AI: The Complete Guide
Verwandte Algorithmen: Dijkstra-Algorithmus, Breitensuche, Bellman-Ford-Algorithmus