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 . . .
. . . .
. . . G2
3
4
5
Python:
def manhattan_distanz(x1, y1, x2, y2):
return abs(x1 - x2) + abs(y1 - y2)2
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 . . .
. . . .
. . . G2
3
4
5
Python:
import math
def euklidische_distanz(x1, y1, x2, y2):
return math.sqrt((x1 - x2)**2 + (y1 - y2)**2)2
3
4
Distanzmetriken im Vergleich
| Metrik | Bewegung | Formel | (0,0)→(3,2) |
|---|---|---|---|
| Manhattan | 4 Richtungen | |Δx| + |Δy| | 5 |
| Euklidisch | Luftlinie | √(Δx² + Δy²) | ≈3.6 |
| Chebyshev | 8 Richtungen | max(|Δx|, |Δy|) | 3 |
Für Labyrinthe: Manhattan (nur 4 Richtungen erlaubt)
Greedy Best-First Search
Idee: Wähle immer Zustand mit niedrigstem h(n)
Bewertung: f(n) = h(n)
Datenstruktur: Liste mit Hilfsfunktion
Implementierung:
def waehle_besten(offene_liste, heuristik):
return min(offene_liste, key=heuristik)2
Eigenschaften:
- ✗ Vollständig: Nein
- ✗ Optimal: Nein (ignoriert zurückgelegte Kosten!)
- ✓ Schnell: Ja (steuert direkt zum Ziel)
. ? . .
? S ? .
. ? . G2
3
4
. . . . . . .
. . . . . . .
. . . . . . .
. # # # # . .
S . . . # . .
. . . . # G .
. . . . # . .2
3
4
5
6
7
Greedy BFS - Algorithmus
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))2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 ]
~~~2
3
4
5
6
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!
A* Search
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
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_neu2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Warum A* optimal ist
Mit zulässiger Heuristik (h ≤ tatsächliche Kosten):
- A* expandiert Knoten in aufsteigender f-Reihenfolge
- Wenn Ziel gefunden → es ist das mit niedrigsten Kosten
- 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*
| Eigenschaft | Greedy BFS | A* |
|---|---|---|
| Bewertung | f(n) = h(n) | f(n) = g(n) + h(n) |
| Zurückgelegte Kosten | Nein | Ja |
| Vollständig | Nein | Ja |
| Optimal | Nein | Ja (mit zulässigem h) |
| Geschwindigkeit | Sehr schnell | Schnell |
| Anwendung | Schnelle Näherung | Optimaler 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)2
3
4
5
Greedy: Wählt nach h (könnte in Sackgasse laufen) A*: Wählt nach f=g+h (findet optimalen Pfad)
Alle Algorithmen im Vergleich
| Algorithmus | Struktur | f(n) | Optimal | Vollständig |
|---|---|---|---|---|
| BFS | Queue (FIFO) | - | Ja* | Ja |
| DFS | Stack (LIFO) | - | Nein | Nein** |
| Greedy BFS | Priority Queue | h(n) | Nein | Nein |
| A* | Priority Queue | g(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
besuchtverfolgen vorgaenger-Dict für Pfad-Rekonstruktion- Python's
heapqfür Priority Queue
Performance:
- Bessere Heuristik = weniger expandierte Knoten
- Zulässigkeit = Optimalität
- Konsistenz = Effizienz
Priority Queue in Python
heapq-Modul:
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")2
3
4
5
6
7
8
9
10
11
Wichtig: Niedrigste Zahl = höchste Priorität!
Zusammenfassung
Kernkonzepte:
- Heuristiken leiten Suche effizient zum Ziel
- Zulässigkeit (h ≤ wahre Kosten) sichert Optimalität
- Greedy BFS schnell aber nicht optimal (nur h)
- A* optimal und effizient (g + h)
- Manhattan-Distanz perfekt für Gitter
A* = Goldstandard für Pfadfindung!