Skip to content

Informierte Suche

Heuristiken & Optimale Pfadfindung


Problem uninformierter Suche

BFS & DFS suchen blind!

  • Kein Wissen über Ziel-Position
  • Erkunden in alle Richtungen gleich
  • Ineffizient für große Räume

Lösung: Nutze Domänenwissen → Heuristiken


Was ist eine Heuristik?

h(n) = Schätzfunktion für Restkosten zum Ziel

Eigenschaften einer guten Heuristik:

  • Zulässig: h(n) ≤ tatsächliche Kosten (nie überschätzen!)
  • Konsistent: h(n) ≤ kosten(n,n') + h(n')
  • Informativ: Je näher an Realität, desto effizienter

Beispiele:

  • Schlechte Heuristik: h(n) = 0 (keine Info → wird zu BFS)
  • Gute Heuristik: Manhattan-Distanz für Gitter

Manhattan-Distanz

Formel: |x₁ - x₂| + |y₁ - y₂|

Anwendung: 4-Richtungs-Bewegung (oben, unten, links, rechts)

Beispiel: Von (0,0) zu (3,2)

Manhattan = |0-3| + |0-2| = 3 + 2 = 5 Schritte

S . . .
. . . .
. . . G

Python:

python
def manhattan_distanz(x1, y1, x2, y2):
    return abs(x1 - x2) + abs(y1 - y2)

Euklidische Distanz

Formel: √((x₁ - x₂)² + (y₁ - y₂)²)

Anwendung: Diagonale Bewegung oder Luftlinie

Beispiel: Von (0,0) zu (3,2)

Euklidisch = √((0-3)² + (0-2)²) = √13 ≈ 3.6

S . . .
. . . .
. . . G

Python:

python
import math

def euklidische_distanz(x1, y1, x2, y2):
    return math.sqrt((x1 - x2)**2 + (y1 - y2)**2)

Distanzmetriken im Vergleich

MetrikBewegungFormel(0,0)→(3,2)
Manhattan4 Richtungen|Δx| + |Δy|5
EuklidischLuftlinie√(Δx² + Δy²)≈3.6
Chebyshev8 Richtungenmax(|Δx|, |Δy|)3

Für Labyrinthe: Manhattan (nur 4 Richtungen erlaubt)


Idee: Wähle immer Zustand mit niedrigstem h(n)

Bewertung: f(n) = h(n)

Datenstruktur: Liste mit Hilfsfunktion

Implementierung:

python
def waehle_besten(offene_liste, heuristik):
    return min(offene_liste, key=heuristik)

Eigenschaften:

  • ✗ Vollständig: Nein
  • ✗ Optimal: Nein (ignoriert zurückgelegte Kosten!)
  • ✓ Schnell: Ja (steuert direkt zum Ziel)

. ? . .
? S ? .
. ? . G

. . . . . . .
. . . . . . .
. . . . . . .
. # # # # . .
S . . . # . .
. . . . # G .
. . . . # . .

Greedy BFS - Algorithmus

python
import heapq

def greedy_bfs(start, ziel, hole_nachbarn, heuristik):
    pq = [(heuristik(start, ziel), start)]
    besucht = [start]

    while pq:
        h, aktuell = heapq.heappop(pq)

        if aktuell == ziel:
            return "Ziel gefunden!"

        for nachbar in hole_nachbarn(aktuell):
            if nachbar not in besucht:
                besucht.append(nachbar)
                h_nachbar = heuristik(nachbar, ziel)
                heapq.heappush(pq, (h_nachbar, nachbar))

Problem von Greedy BFS

Sieht nur voraus, nicht zurück!

~~~graph-easy --as=boxart
[ S ] - 1 -> [ A ]
[ A ] - 100 -> [ G ]
[ S ] - 10 -> [ B ]
[ B ] - 10 -> [ G ]
~~~

h(S)=1, h(A)=0.5, h(B)=10, h(G)=0

Greedy wählt:

  • Von S: h(A)=0.5, h(B)=10 → wählt A (viel besser!)
  • Von A: nur G erreichbar → wählt G
  • Pfad: S→A→G, Kosten = 101 ✗

Optimal wäre: S→B→G, Kosten = 20 ✓

→ Nicht optimal, weil Greedy die tatsächlichen Kosten ignoriert!


Idee: Kombiniere g(n) + h(n)

f(n) = g(n) + h(n)

  • g(n): Tatsächliche Kosten von Start zu n
  • h(n): Geschätzte Kosten von n zu Ziel
  • f(n): Geschätzte Gesamtkosten über n

Balanciert: Berücksichtigt sowohl Vergangenheit als auch Zukunft!


A* - Eigenschaften

Vollständig: Ja (findet immer Lösung) ✓ Optimal: Ja (wenn h zulässig ist!) ✓ Effizient: Optimal-effizient ✗ Speicher: O(b^d) - kann viel benötigen

Speicherkomplexität: O(b^d) Zeitkomplexität: O(b^d)

Wenn h(n) zulässig → A garantiert kürzesten Pfad!*


A* - Algorithmus

python
def waehle_besten_a_stern(offen, g, ziel, heuristik):
    return min(offen, key=lambda n: g[n] + heuristik(n, ziel))

def a_stern(start, ziel, hole_nachbarn, heuristik):
    g = {start: 0}
    offen = [start]
    besucht = []

    while offen:
        aktuell = waehle_besten_a_stern(offen, g, ziel, heuristik)
        offen.remove(aktuell)

        if aktuell == ziel:
            return "Ziel gefunden!"

        besucht.append(aktuell)

        for nachbar in hole_nachbarn(aktuell):
            g_neu = g[aktuell] + 1

            if nachbar not in besucht and nachbar not in offen:
                g[nachbar] = g_neu
                offen.append(nachbar)
            elif nachbar in offen and g_neu < g[nachbar]:
                g[nachbar] = g_neu

Warum A* optimal ist

Mit zulässiger Heuristik (h ≤ tatsächliche Kosten):

  1. A* expandiert Knoten in aufsteigender f-Reihenfolge
  2. Wenn Ziel gefunden → es ist das mit niedrigsten Kosten
  3. Kein besserer Pfad wurde übersehen

Beweis-Intuition: Falls optimaler Pfad existiert mit C₂ < C₁, dann wäre f(n) auf optimalem Pfad kleiner → A* hätte ihn zuerst expandiert!


Zulässige Heuristiken

h(n) ≤ tatsächliche_kosten(n, ziel)

Beispiele:

✓ Manhattan-Distanz (4-Richtungen) ✓ Euklidische Distanz ✓ Luftlinien-Distanz ✗ 2 × Manhattan (überschätzt!) ✗ h(n) = 0 (zulässig, aber nicht informativ)

Wichtig: Zulässigkeit garantiert Optimalität!


Greedy BFS vs. A*

EigenschaftGreedy BFSA*
Bewertungf(n) = h(n)f(n) = g(n) + h(n)
Zurückgelegte KostenNeinJa
VollständigNeinJa
OptimalNeinJa (mit zulässigem h)
GeschwindigkeitSehr schnellSchnell
AnwendungSchnelle NäherungOptimaler Pfad

Beispiel: Labyrinth

S(g=0,h=6,f=6) → .(g=1,h=5,f=6) → .(g=2,h=4,f=6)
.              X                  .
.              .                  .
.              X                  X
.              .                  G(g=3,h=0,f=3)

Greedy: Wählt nach h (könnte in Sackgasse laufen) A*: Wählt nach f=g+h (findet optimalen Pfad)


Alle Algorithmen im Vergleich

AlgorithmusStrukturf(n)OptimalVollständig
BFSQueue (FIFO)-Ja*Ja
DFSStack (LIFO)-NeinNein**
Greedy BFSPriority Queueh(n)NeinNein
A*Priority Queueg(n)+h(n)Ja***Ja

* gleiche Kosten ** unendliche Räume *** zulässige Heuristik


Wann welchen Algorithmus?

BFS: Kürzester Pfad, kleine Räume, ungewichtet

DFS: Tiefe Lösungen, Speicher begrenzt

Greedy BFS: Schnelle Näherung, Zeit kritisch

A*: Optimaler Pfad benötigt, gute Heuristik verfügbar

→ A* ist meist die beste Wahl für Pfadfindung!


Praktische Tipps

Heuristik wählen:

  • Gitter (4-Richtungen) → Manhattan
  • Gitter (8-Richtungen) → Chebyshev
  • Kontinuierlich → Euklidisch

Implementierung:

  • Immer besucht verfolgen
  • vorgaenger-Dict für Pfad-Rekonstruktion
  • Python's heapq für Priority Queue

Performance:

  • Bessere Heuristik = weniger expandierte Knoten
  • Zulässigkeit = Optimalität
  • Konsistenz = Effizienz

Priority Queue in Python

heapq-Modul:

python
import heapq

pq = []  # Leere Priority Queue

# Element hinzufügen: (priorität, wert)
heapq.heappush(pq, (5, "niedrig"))
heapq.heappush(pq, (1, "hoch"))
heapq.heappush(pq, (3, "mittel"))

# Niedrigste Priorität entfernen
priorität, wert = heapq.heappop(pq)  # (1, "hoch")

Wichtig: Niedrigste Zahl = höchste Priorität!


Zusammenfassung

Kernkonzepte:

  1. Heuristiken leiten Suche effizient zum Ziel
  2. Zulässigkeit (h ≤ wahre Kosten) sichert Optimalität
  3. Greedy BFS schnell aber nicht optimal (nur h)
  4. A* optimal und effizient (g + h)
  5. Manhattan-Distanz perfekt für Gitter

A* = Goldstandard für Pfadfindung!

Informatik & ICT Unterricht Neufeld