Musterlösung Praktikum 9
This commit is contained in:
parent
1692129d89
commit
cae3355bf1
186
praktika/09_mst/aufgabe_kruskal.py
Normal file
186
praktika/09_mst/aufgabe_kruskal.py
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import math
|
||||||
|
import sys
|
||||||
|
from itertools import combinations
|
||||||
|
|
||||||
|
from vorlesung.L08_graphen.graph import AdjacencyListGraph
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# b) Union-Find
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class DisjointSet:
|
||||||
|
"""Union-Find mit Pfadkompression und Union-by-Size."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._parent = {}
|
||||||
|
self._size = {}
|
||||||
|
|
||||||
|
def make_set(self, v):
|
||||||
|
self._parent[v] = v
|
||||||
|
self._size[v] = 1
|
||||||
|
|
||||||
|
def find_set(self, v):
|
||||||
|
if self._parent[v] != v:
|
||||||
|
self._parent[v] = self.find_set(self._parent[v]) # Pfadkompression
|
||||||
|
return self._parent[v]
|
||||||
|
|
||||||
|
def union(self, u, v):
|
||||||
|
"""Vereinigt die Schaltkreise von u und v.
|
||||||
|
Gibt True zurück, wenn sie vorher getrennt waren."""
|
||||||
|
ru, rv = self.find_set(u), self.find_set(v)
|
||||||
|
if ru == rv:
|
||||||
|
return False
|
||||||
|
if self._size[ru] < self._size[rv]:
|
||||||
|
ru, rv = rv, ru
|
||||||
|
self._parent[rv] = ru
|
||||||
|
self._size[ru] += self._size[rv]
|
||||||
|
return True
|
||||||
|
|
||||||
|
def size(self, v):
|
||||||
|
return self._size[self.find_set(v)]
|
||||||
|
|
||||||
|
def circuit_sizes(self):
|
||||||
|
"""Alle Schaltkreisgrößen, absteigend sortiert."""
|
||||||
|
return sorted(
|
||||||
|
[self._size[v] for v in self._parent if self._parent[v] == v],
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Hilfsfunktionen
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def euclidean(a, b):
|
||||||
|
return math.sqrt(sum((ai - bi) ** 2 for ai, bi in zip(a, b)))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_boxes(text):
|
||||||
|
boxes = []
|
||||||
|
for line in text.strip().splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
boxes.append(tuple(int(x) for x in line.split(',')))
|
||||||
|
return boxes
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# c–e) Kruskal
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def solve(text, k=1000):
|
||||||
|
boxes = parse_boxes(text)
|
||||||
|
n = len(boxes)
|
||||||
|
print(f"Anzahl Boxen: {n}")
|
||||||
|
print(f"Potenzielle Verbindungen: {n * (n - 1) // 2}")
|
||||||
|
|
||||||
|
# a) Alle Paare nach Distanz sortiert
|
||||||
|
edges = sorted(
|
||||||
|
((euclidean(a, b), a, b) for a, b in combinations(boxes, 2)),
|
||||||
|
key=lambda e: e[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
ds = DisjointSet()
|
||||||
|
for b in boxes:
|
||||||
|
ds.make_set(b)
|
||||||
|
|
||||||
|
# --- Teil 1: die k kürzesten Paare verbinden (inkl. No-Ops) ---
|
||||||
|
for _, u, v in edges[:k]:
|
||||||
|
ds.union(u, v)
|
||||||
|
idx = k
|
||||||
|
|
||||||
|
sizes = ds.circuit_sizes()
|
||||||
|
top3 = sizes[:3]
|
||||||
|
result1 = top3[0] * top3[1] * top3[2]
|
||||||
|
print(f"\n--- Teil 1 (k={k}) ---")
|
||||||
|
print(f"Schaltkreisgrößen: {sizes}")
|
||||||
|
print(f"Produkt der 3 größten: {top3[0]} × {top3[1]} × {top3[2]} = {result1}")
|
||||||
|
|
||||||
|
# --- Teil 2: weiter bis ein einziger Schaltkreis ---
|
||||||
|
last_u = last_v = None
|
||||||
|
while idx < len(edges):
|
||||||
|
_, u, v = edges[idx]
|
||||||
|
idx += 1
|
||||||
|
if ds.union(u, v):
|
||||||
|
last_u, last_v = u, v
|
||||||
|
if ds.size(last_u) == n:
|
||||||
|
break
|
||||||
|
|
||||||
|
result2 = last_u[0] * last_v[0]
|
||||||
|
print(f"\n--- Teil 2 ---")
|
||||||
|
print(f"Letzte Verbindung: {last_u} -- {last_v}")
|
||||||
|
print(f"Produkt der X-Koordinaten: {last_u[0]} × {last_v[0]} = {result2}")
|
||||||
|
|
||||||
|
# --- Verifikation mit graph.mst_kruskal() ---
|
||||||
|
# mst_kruskal() liefert alle MST-Kanten aufsteigend nach Gewicht sortiert.
|
||||||
|
# Die letzte Kante ist die schwerste im MST = die zuletzt hinzugefügte
|
||||||
|
# Kante bei Kruskal = identisch mit dem Ergebnis aus Teil 2.
|
||||||
|
# connect() ist gerichtet; beide Richtungen werden eingetragen, damit
|
||||||
|
# all_edges() alle Paare sieht. Der Same-Set-Check in mst_kruskal()
|
||||||
|
# filtert die Rückrichtung als No-Op heraus.
|
||||||
|
g = AdjacencyListGraph()
|
||||||
|
for b in boxes:
|
||||||
|
g.insert_vertex(f"{b[0]},{b[1]},{b[2]}")
|
||||||
|
for a, b in combinations(boxes, 2):
|
||||||
|
name_a = f"{a[0]},{a[1]},{a[2]}"
|
||||||
|
name_b = f"{b[0]},{b[1]},{b[2]}"
|
||||||
|
d = euclidean(a, b)
|
||||||
|
g.connect(name_a, name_b, d)
|
||||||
|
g.connect(name_b, name_a, d)
|
||||||
|
mst_edges, _ = g.mst_kruskal()
|
||||||
|
last = mst_edges[-1]
|
||||||
|
gu = tuple(int(x) for x in last[0].split(','))
|
||||||
|
gv = tuple(int(x) for x in last[1].split(','))
|
||||||
|
result2_graph = gu[0] * gv[0]
|
||||||
|
assert result2_graph == result2, (
|
||||||
|
f"Verifikation fehlgeschlagen: graph.mst_kruskal() → {result2_graph}, "
|
||||||
|
f"eigene Lösung → {result2}"
|
||||||
|
)
|
||||||
|
print(f"Verifikation mit graph.mst_kruskal() bestanden.")
|
||||||
|
|
||||||
|
return result1, result2
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Beispieldaten (AoC 2025, Tag 8 – 20 Junction Boxes)
|
||||||
|
# Erwartetes Ergebnis: Teil 1 (k=10) → 40, Teil 2 → 25272
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
BEISPIEL = """
|
||||||
|
162,817,812
|
||||||
|
57,618,57
|
||||||
|
906,360,560
|
||||||
|
592,479,940
|
||||||
|
352,342,300
|
||||||
|
466,668,158
|
||||||
|
542,29,236
|
||||||
|
431,825,988
|
||||||
|
739,650,466
|
||||||
|
52,470,668
|
||||||
|
216,146,977
|
||||||
|
819,987,18
|
||||||
|
117,168,530
|
||||||
|
805,96,715
|
||||||
|
346,949,466
|
||||||
|
970,615,88
|
||||||
|
941,993,340
|
||||||
|
862,61,35
|
||||||
|
984,92,344
|
||||||
|
425,690,689
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
print("=== Selbsttest mit Beispieldaten ===")
|
||||||
|
r1, r2 = solve(BEISPIEL, k=10)
|
||||||
|
assert r1 == 40, f"Teil 1 erwartet 40, erhalten {r1}"
|
||||||
|
assert r2 == 25272, f"Teil 2 erwartet 25272, erhalten {r2}"
|
||||||
|
print("Selbsttest bestanden.")
|
||||||
|
else:
|
||||||
|
filename = sys.argv[1]
|
||||||
|
k = int(sys.argv[2]) if len(sys.argv) > 2 else 1000
|
||||||
|
with open(filename) as f:
|
||||||
|
text = f.read()
|
||||||
|
solve(text, k)
|
||||||
Loading…
x
Reference in New Issue
Block a user