Pathfinding-Algorithmus-Visualizer

verfolge, wie verschiedene Suchalgorithmen Front für Front über dasselbe Gitter rennen

Interaktive Simulation wird geladen...

Lektion

Die Theorie — Pathfinding-Algorithmus-Visualizer

Drei der vier Algorithmen im Dropdown-Menü sind derselbe Algorithmus. Sie alle bewerten die Zellen, die darauf warten, expandiert zu werden, mit einem einzigen Ausdruck: f(n) = g(n) + h(n) — bisherige Kosten plus geschätzte verbleibende Kosten — und sie unterscheiden sich nur darin, welchen der beiden Terme sie beibehalten. Lässt man h weg, erhält man Dijkstra. Ignoriert man g, erhält man Greedy Best-First. Der vierte, die Breitensuche, ist das, wozu Dijkstra zusammenschrumpft, wenn jeder Schritt gleich viel kostet.

Was die einzelnen Symbole bedeuten

g(n)
die Kosten der günstigsten bisher gefundenen Route vom Start zur Zelle n. Ein seitlicher oder vertikaler Schritt kostet 1; eine Diagonale kostet √2, da der Schritt als Länge gemessen und nicht als eine einzelne Bewegung gezählt wird.
h(n)
die Schätzung der noch verbleibenden Kosten. Für Dijkstra und die Breitensuche wird sie nicht bloß ungenutzt gelassen — sie ist Null, und genau das macht sie zu demselben Ausdruck mit einem fehlenden Term.
f(n)
der Sortierschlüssel: Die Zelle mit dem kleinsten f wird als Nächstes expandiert. Er wird oberhalb des Gitters angezeigt, wobei die eigenen Zahlen der aktuellen Zelle eingesetzt sind, sodass die gerade getroffene Wahl des Algorithmus sichtbar ist, anstatt bloß behauptet zu werden.
frontier
die bernsteinfarbenen Zellen — entdeckt, aber noch nicht expandiert. Expandierte Knoten zählt, was diese Menge verlassen hat, was es zu einem Maß für die geleistete Arbeit macht und nicht für die Qualität der Antwort.

Woher die Formel kommt

  1. Lege fest, was „kürzester“ bedeutet, bevor du einen Algorithmus wählst. Die Kosten eines Pfades sind die Summe seiner Schrittlängen: 1 für eine seitliche oder vertikale Bewegung, √2 für eine Diagonale. Zellen werden nicht gezählt; Entfernungen werden addiert.
  2. Nun die Schleife, die für alle vier identisch ist: Entnimm eine Zelle aus der Front, markiere sie als besucht, und biete jedem passierbaren Nachbarn die Rückkehr in die Front mit einem aktualisierten g an. Beachte, was diese Schleife nie erwähnt — eine Heuristik, eine Zielrichtung oder welchen Algorithmus du gewählt hast.
  3. Die einzige verbleibende Entscheidung ist also, welche Zelle entnommen werden soll, und sie besteht aus einem einzigen Ausdruck. Sortiere nach g + h, und du erhältst A*. Setze h = 0, und die Sortierung erfolgt allein nach den bisherigen Kosten — das ist Dijkstra, der sich in Ringen nach außen ausbreitet, weil er keine Ahnung hat, wo das Ziel liegt. Ignorierst du stattdessen g, erfolgt die Sortierung allein nach der Schätzung — Greedy Best-First, das direkt auf das Ziel zusteuert und jede Route akzeptiert, auf der es angekommen ist.
  4. Die Breitensuche bewertet überhaupt nicht: First in, first out. Ist Diagonalen erlauben deaktiviert, kostet jeder Schritt exakt 1, sodass die Reihenfolge, in der die Zellen eintreffen, die Reihenfolge steigender g-Werte ist — und die Breitensuche expandiert daher genau das, was Dijkstra expandiert. Du kannst es bestätigen, ohne das Gitter neu zu generieren: Wechsle zwischen den beiden, und Expandierte Knoten bewegt sich kein Stück. Schaltest du Diagonalen ein, bricht das Argument in sich zusammen, da eine Diagonale √2 statt 1 kostet und die Ankunftsreihenfolge die Kosten nicht mehr abbildet.

So liest du, was du siehst

Der Prioritätswert steht oberhalb des Gitters, wobei g und h der aktuellen Zelle eingesetzt sind, sodass du die Bewertung ablesen kannst, die zur letzten Entscheidung geführt hat. Darunter befinden sich zwei Zähler: Expandierte Knoten und Pfadlänge. Bernstein steht für die Front, Indigo für bereits besucht. Das Ändern des Algorithmus generiert kein neues Gitter, und genau das macht den Vergleich überhaupt wertvoll — auf demselben Vier-Wege-Layout ist Dijkstras Expandierte Knoten niemals kleiner als der von A*, und der von Greedy Best-First ist ein kleiner Bruchteil von beiden.

Setzt voraus
Schrittkosten, die niemals negativ sind, und ein Gitter, das sich während der Suche nicht ändert. Der Start ist immer die Zelle oben links und das Ziel die unten rechts. Das Schließen einer Zelle ist endgültig — die Schleife zieht eine Zelle nie erneut in Betracht — und das ist nur deshalb sicher, weil keine spätere Route günstiger ankommen kann, wenn jeder Schritt einen nicht-negativen Betrag hinzufügt.
Versagt, wenn
Expandierte Knoten ist die Zahl, die sich am dramatischsten verändert, und sie ist kein Maß für Qualität. Wähle Greedy Best-First: Auf demselben Gitter expandiert es einen kleinen Bruchteil dessen, was A* tut, und auf den meisten Layouts liefert es eine längere Pfadlänge zurück. Generiere einige neue Gitter und beobachte, wie sich die beiden Zähler in entgegengesetzte Richtungen bewegen. Weniger Zellen zu untersuchen ist eine Aussage darüber, wie hart der Algorithmus gearbeitet hat, und niemals darüber, wie gut seine Antwort war.

warum Heuristiken wichtig sind 🖖

Die Formel f(n)=g(n)+h(n) lässt A* Knoten bevorzugen, die bisher günstig sind und dem Ziel noch nahe erscheinen. Solange h die verbleibenden Kosten nie überschätzt, behält A* seine Garantie auf den kürzesten Pfad; eine stärkere zulässige Heuristik h durchsucht weniger unnötige Zellen. Wechsle das Algorithmus-Dropdown, um zu sehen, was passiert, wenn man die Heuristik weglässt (Dijkstra), die bisherigen Kosten weglässt (Greedy) oder ganz auf Bewertung verzichtet (BFS).

Eine Schleife, vier Charaktere 🖖

Alle vier Algorithmen durchlaufen exakt dieselbe Schleife: Sie nehmen das aussichtsreichste Feld aus einer Warteliste, der bernsteinfarbenen Suchfront, markieren es als besucht (indigoblau) und fügen seine freien Nachbarfelder wieder der Liste hinzu. Sie unterscheiden sich nur darin, wie sie das aussichtsreichste Feld bestimmen. Die Anzahl der expandierten Knoten ist dabei die entscheidende Kennzahl und verhält sich überraschend. In einem leeren Gitter expandiert A* jedes einzelne Feld. Mit der Manhattan-Distanz erhalten alle monotonen Wege zur Ecke denselben f-Wert, sodass keiner bevorzugt wird. Hindernisse lassen die Anzahl sinken.

Diagonalen können die Garantie brechen 🖖

Aktiviere 'Diagonale erlauben' zusammen mit der Manhattan-Heuristik, und A* kann klammheimlich einen Pfad liefern, der nicht der kürzeste ist. Ein Diagonalschritt kostet hier nur √2 ≈ 1.41, doch die Manhattan-Distanz berechnet ihn als 2 — die Heuristik überschätzt also, wird unzulässig, und die Optimalitätsgarantie ist dahin. Wechsle zu Euklidisch, das die Luftlinie nie überschätzt, und der kürzeste Pfad kehrt zurück.

Lernpfad

Arbeit zählen, nicht Sekunden

Quellen (3)

Beispielaufgaben

  • offenes Gitter - Ein 20×20-Gitter, zu einem Zehntel blockiert, und A* expandiert noch immer den größten Teil: Jeder monotone Weg hat f = 38, sodass der Gleichstand bestehen bleibt.
  • labyrinthartig - Unübersichtlichkeit macht A* schneller. Eine Blockierungsdichte von 0,28 löst die Gleichstände auf, die ein offenes Gitter aufwendig machen; die Suche benötigt nur etwa die Hälfte der freien Felder.
  • schwache Heuristik - In einem offenen Gitter mit 4er-Nachbarschaft ist die Manhattan-Distanz exakt, während die euklidische Distanz stets darunterliegt. Damit ist sie die schwächere der beiden zulässigen Heuristiken. Bei gleichem Weg werden etwa ein Drittel mehr Felder expandiert.
  • Dijkstra erkundet - Dasselbe 24×24-Gitter mit derselben Dichte von 0,22 wie bei „Schlechte Heuristik“, diesmal ganz ohne Heuristik. Ohne Kenntnis des Ziels expandiert Dijkstra etwa anderthalbmal so viele Felder wie A*.
  • Greedy-Falle - Die Greedy-Suche bewertet nur nach h, steuert daher direkt auf das Ziel zu und expandiert weniger als halb so viele Felder wie A*. Meist findet sie dafür einen längeren Weg.
  • BFS + Diagonalen - Die Breitensuche kennt die Lage des Ziels nicht und erreicht deshalb fast jedes freie Feld. Sind Diagonalen erlaubt, zählt auch ein diagonaler Zug als ein Schritt. Minimiert wird somit die Anzahl der Schritte, unabhängig von ihrer Länge.