Musterlösung Praktikum 8

This commit is contained in:
Oliver Hofmann 2026-06-16 11:10:54 +02:00
parent 586a52f9a5
commit 1692129d89
2 changed files with 166 additions and 0 deletions

View File

@ -0,0 +1,84 @@
import time
from vorlesung.L08_graphen.graph import AdjacencyListGraph
# AoC 2024, Tag 16 erstes Beispiel-Labyrinth (Antwort: 7036)
GRID = """\
###############
#.......#....E#
#.#.###.#.###.#
#.....#.#...#.#
#.###.#####.#.#
#.#.#.......#.#
#.#.#####.###.#
#...........#.#
###.#.#####.#.#
#...#.....#.#.#
#.#.#.###.#.#.#
#.....#...#.#.#
#.###.#.#.#.#.#
#S..#.....#...#
###############""".strip().split('\n')
# Richtungen: 0=N, 1=O, 2=S, 3=W (O = Ost = Startrichtung des Rentiers)
DR = [-1, 0, 1, 0]
DC = [0, 1, 0, -1]
def build_graph(grid):
g = AdjacencyListGraph()
rows, cols = len(grid), len(grid[0])
start = end = None
for r in range(rows):
for c in range(cols):
if grid[r][c] == '#':
continue
if grid[r][c] == 'S':
start = (r, c)
elif grid[r][c] == 'E':
end = (r, c)
for d in range(4):
g.insert_vertex(f"{r},{c},{d}")
for r in range(rows):
for c in range(cols):
if grid[r][c] == '#':
continue
for d in range(4):
src = f"{r},{c},{d}"
# Vorwärtsbewegen (Kosten 1)
nr, nc = r + DR[d], c + DC[d]
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] != '#':
g.connect(src, f"{nr},{nc},{d}", 1)
# Drehen links und rechts (Kosten 1000 je Drehung)
g.connect(src, f"{r},{c},{(d - 1) % 4}", 1000)
g.connect(src, f"{r},{c},{(d + 1) % 4}", 1000)
return g, start, end
# --- a) + b) Graph aufbauen und Dijkstra ausführen ---
g, start, end = build_graph(GRID)
v_count = len(list(g.all_vertices()))
e_count = len(g.all_edges())
print(f"Graph: |V| = {v_count}, |E| = {e_count}")
start_state = f"{start[0]},{start[1]},1" # Startposition, Blickrichtung Ost (1)
dist_map, pred_map = g.dijkstra(start_state)
# Bestes Zielfeld: E in beliebiger Richtung (4 zulässige Zielzustände)
end_vertices = [g.get_vertex(f"{end[0]},{end[1]},{d}") for d in range(4)]
best_end = min(end_vertices, key=lambda v: dist_map[v])
print(f"Gesamtkosten: {dist_map[best_end]}") # Erwartung: 11048
pfad = g.path(best_end.value, pred_map)
print(f"Pfad: {len(pfad)} Zustände ({len(pfad) - 1} Schritte)")
print("Erste Schritte:", " -> ".join(pfad[:5]), "...")
print("Letzte Schritte: ...", " -> ".join(pfad[-3:]))
# --- c) Laufzeitmessung ---
t0 = time.perf_counter()
g.dijkstra(start_state)
t1 = time.perf_counter()
print(f"\nLaufzeit (Beispiel): {(t1 - t0) * 1000:.3f} ms")

View File

@ -0,0 +1,82 @@
from vorlesung.L08_graphen.graph import AdjacencyListGraph
# Korrigierte Kantenliste (Vorlesungsbeispiel).
# Hinweis: Das Arbeitsblatt enthält c->b:-2, c->e:5, d->c:-5, e->c:4
# diese Werte erzeugen einen negativen Zyklus (b->c->b: 1-2=-1) im
# Basisgraphen und müssen in der Aufgabenstellung korrigiert werden.
# Korrekte Werte (kein Vorzyklus, Ergebnis: b=7, c=2, d=4, e=6):
EDGES = [
('a', 'b', 9),
('a', 'd', 4),
('b', 'c', 1),
('b', 'd', 2),
('c', 'b', 5), # war -2 im Entwurf
('c', 'e', 4), # war 5 im Entwurf
('d', 'c', -2), # war -5 im Entwurf
('d', 'e', 2),
]
def bellman_ford(graph, start_name):
"""Bellman-Ford: kürzeste Wege mit Erkennung negativer Zyklen.
Gibt (distance_map, predecessor_map, has_negative_cycle) zurück.
|V|-1 Relaxationsdurchläufe über alle Kanten, dann ein Prüfdurchlauf.
"""
distance_map = {}
predecessor_map = {}
for v in graph.all_vertices():
distance_map[v] = float('inf')
predecessor_map[v] = None
start = graph.get_vertex(start_name)
distance_map[start] = 0
n = len(list(graph.all_vertices()))
for _ in range(n - 1):
for src_name, dst_name, weight in graph.all_edges():
src = graph.get_vertex(src_name)
dst = graph.get_vertex(dst_name)
if distance_map[src] + weight < distance_map[dst]:
distance_map[dst] = distance_map[src] + weight
predecessor_map[dst] = src
# Prüfdurchlauf: jede weitere Verbesserung zeigt einen negativen Zyklus
has_negative_cycle = False
for src_name, dst_name, weight in graph.all_edges():
src = graph.get_vertex(src_name)
dst = graph.get_vertex(dst_name)
if distance_map[src] + weight < distance_map[dst]:
has_negative_cycle = True
break
return distance_map, predecessor_map, has_negative_cycle
# --- a) + b) Basisgraph ---
g = AdjacencyListGraph()
for v in ['a', 'b', 'c', 'd', 'e']:
g.insert_vertex(v)
for src, dst, w in EDGES:
g.connect(src, dst, w)
dist, pred, neg_cycle = bellman_ford(g, 'a')
print(f"Negativer Zyklus: {neg_cycle}")
print("Kürzeste Distanzen:")
for name in ['a', 'b', 'c', 'd', 'e']:
v = g.get_vertex(name)
pfad = g.path(name, pred)
print(f" {name}: d={dist[v]}, Pfad: {' -> '.join(pfad)}")
# --- d) Komplexität ---
v_count = len(list(g.all_vertices()))
e_count = len(g.all_edges())
print(f"\n|V| = {v_count}, |E| = {e_count}")
# --- c) Negativer Zyklus ---
print("\n--- Mit negativem Zyklus (e -> b, Gewicht -8) ---")
g.connect('e', 'b', -8)
dist2, pred2, neg_cycle2 = bellman_ford(g, 'a')
print(f"Negativer Zyklus: {neg_cycle2}")
# Betroffener Zyklus: b -> c -> e -> b (1 + 4 + (-8) = -3 < 0)