diff --git a/praktika/08_kuerzeste_wege/aufgabe1_dijkstra.py b/praktika/08_kuerzeste_wege/aufgabe1_dijkstra.py new file mode 100644 index 0000000..f90b654 --- /dev/null +++ b/praktika/08_kuerzeste_wege/aufgabe1_dijkstra.py @@ -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") diff --git a/praktika/08_kuerzeste_wege/aufgabe2_bellman_ford.py b/praktika/08_kuerzeste_wege/aufgabe2_bellman_ford.py new file mode 100644 index 0000000..8d0db2a --- /dev/null +++ b/praktika/08_kuerzeste_wege/aufgabe2_bellman_ford.py @@ -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)