Musterlösung Praktikum 5
This commit is contained in:
parent
5aca875fa6
commit
ed329d5d85
1
.gitignore
vendored
1
.gitignore
vendored
@ -23,3 +23,4 @@ docs/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
/.agents/
|
||||
|
||||
62
praktika/05_b_baum/aufgabe1_graphviz.py
Normal file
62
praktika/05_b_baum/aufgabe1_graphviz.py
Normal file
@ -0,0 +1,62 @@
|
||||
import sys
|
||||
import os
|
||||
_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
_l06 = os.path.join(_root, 'vorlesung', 'L06_b_baeume')
|
||||
if _l06 not in sys.path: sys.path.insert(0, _l06)
|
||||
if _root not in sys.path: sys.path.insert(0, _root)
|
||||
|
||||
import graphviz
|
||||
from datetime import datetime
|
||||
from b_tree import BTree
|
||||
from utils.algo_context import AlgoContext
|
||||
from utils.algo_array import Array
|
||||
|
||||
|
||||
class BTreeVis(BTree):
|
||||
"""BTree-Erweiterung mit Graphviz-Ausgabe als Unterklasse."""
|
||||
|
||||
def graph_traversal(self):
|
||||
def _node_rep(node):
|
||||
label = " | ".join(str(node.value[i]) for i in range(node.n))
|
||||
dot.node(str(id(node)), label=label, shape="record", fontname="Arial")
|
||||
|
||||
def _rec(node):
|
||||
_node_rep(node)
|
||||
if not node.leaf:
|
||||
for i in range(node.n + 1):
|
||||
child = node.children[i]
|
||||
if child is not None:
|
||||
dot.edge(str(id(node)), str(id(child)))
|
||||
_rec(child)
|
||||
|
||||
dot = graphviz.Digraph(
|
||||
name=f"BTree_m{self.m}",
|
||||
engine="dot",
|
||||
node_attr={"fontname": "Arial"},
|
||||
format="pdf",
|
||||
)
|
||||
_rec(self.root)
|
||||
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
dot.render(f"BTree_m{self.m}_{ts}.gv")
|
||||
|
||||
|
||||
ctx = AlgoContext()
|
||||
values = Array.from_file('data/seq1.txt', ctx)
|
||||
tree = BTreeVis(3, ctx)
|
||||
for cell in values:
|
||||
tree.insert(cell)
|
||||
|
||||
# graph_traversal() erzeugt eine DOT-Datei und rendert sie als PDF.
|
||||
tree.graph_traversal()
|
||||
|
||||
# ── Antworten zu den Beobachtungsfragen ──────────────────────────────────────
|
||||
|
||||
# Wie viele Schlüssel hat die Wurzel?
|
||||
# Ablesbar direkt aus tree.root.n.
|
||||
print("Schlüssel in der Wurzel:", tree.root.n)
|
||||
|
||||
# Liegen alle Blätter auf derselben Tiefe?
|
||||
# Ja – das ist eine B-Baum-Invariante: alle Blätter haben immer
|
||||
# dieselbe Tiefe, da Splits ausschließlich aufwärts propagieren.
|
||||
# In der PDF sind alle Blattknoten auf der untersten Ebene.
|
||||
print("Höhe des Baums:", tree.height())
|
||||
51
praktika/05_b_baum/aufgabe2_eigenschaften.py
Normal file
51
praktika/05_b_baum/aufgabe2_eigenschaften.py
Normal file
@ -0,0 +1,51 @@
|
||||
import sys
|
||||
import os
|
||||
_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
_l06 = os.path.join(_root, 'vorlesung', 'L06_b_baeume')
|
||||
if _l06 not in sys.path: sys.path.insert(0, _l06)
|
||||
if _root not in sys.path: sys.path.insert(0, _root)
|
||||
|
||||
from b_tree import BTree
|
||||
from utils.algo_context import AlgoContext
|
||||
from utils.algo_array import Array
|
||||
|
||||
ctx = AlgoContext()
|
||||
values = Array.from_file('data/seq3.txt', ctx)
|
||||
|
||||
tree3 = BTree(3, ctx)
|
||||
tree5 = BTree(5, ctx)
|
||||
for cell in values:
|
||||
tree3.insert(cell)
|
||||
tree5.insert(cell)
|
||||
|
||||
# ── Welche Höhe ergibt sich für Ordnung 3 bzw. 5? ───────────────────────────
|
||||
print("Höhe Ordnung 3:", tree3.height())
|
||||
print("Höhe Ordnung 5:", tree5.height())
|
||||
|
||||
# Ordnung 3: Knoten fassen 2–5 Schlüssel → weniger Schlüssel pro Knoten,
|
||||
# mehr Ebenen nötig. seq3.txt hat 10 000 Werte → Höhe ca. 5–7.
|
||||
# Ordnung 5: Knoten fassen 4–9 Schlüssel → geringere Höhe, ca. 4–5.
|
||||
|
||||
# ── Traversierung und Sortierungsprüfung ─────────────────────────────────────
|
||||
results3 = []
|
||||
tree3.traversal(lambda v: results3.append(int(str(v))))
|
||||
print("Sortiert (Ordnung 3):", results3 == sorted(results3))
|
||||
|
||||
results5 = []
|
||||
tree5.traversal(lambda v: results5.append(int(str(v))))
|
||||
print("Sortiert (Ordnung 5):", results5 == sorted(results5))
|
||||
|
||||
# In-Order-Traversal eines B-Baums liefert immer eine sortierte Folge:
|
||||
# Die B-Baum-Eigenschaft (linker Teilbaum < Schlüssel < rechter Teilbaum)
|
||||
# gilt rekursiv für jeden Knoten.
|
||||
|
||||
# ── Knoten mit Wert 0 suchen ─────────────────────────────────────────────────
|
||||
node3 = tree3.search(0)
|
||||
node5 = tree5.search(0)
|
||||
print("Knoten mit 0 (Ordnung 3):", str(node3) if node3 else "nicht gefunden")
|
||||
print("Knoten mit 0 (Ordnung 5):", str(node5) if node5 else "nicht gefunden")
|
||||
|
||||
# search() gibt den Knoten zurück, der den gesuchten Schlüssel enthält.
|
||||
# __str__ gibt alle Schlüssel des Knotens aus, z.B.: ( -2 0 3 )
|
||||
# Größere Ordnung → mehr Schlüssel pro Knoten → der Knoten mit 0
|
||||
# enthält bei m=5 tendenziell mehr Nachbarn als bei m=3.
|
||||
89
praktika/05_b_baum/aufgabe3_disk_io.py
Normal file
89
praktika/05_b_baum/aufgabe3_disk_io.py
Normal file
@ -0,0 +1,89 @@
|
||||
import sys
|
||||
import os
|
||||
_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
_l06 = os.path.join(_root, 'vorlesung', 'L06_b_baeume')
|
||||
if _l06 not in sys.path: sys.path.insert(0, _l06)
|
||||
if _root not in sys.path: sys.path.insert(0, _root)
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
from b_tree import BTree
|
||||
from b_tree_node import BTreeNode
|
||||
from utils.algo_context import AlgoContext
|
||||
from utils.algo_array import Array
|
||||
|
||||
|
||||
def count_loads(node: BTreeNode) -> int:
|
||||
if node is None:
|
||||
return 0
|
||||
total = node.loaded_count
|
||||
for child in node.children:
|
||||
total += count_loads(child)
|
||||
return total
|
||||
|
||||
|
||||
def count_saves(node: BTreeNode) -> int:
|
||||
if node is None:
|
||||
return 0
|
||||
total = node.saved_count
|
||||
for child in node.children:
|
||||
total += count_saves(child)
|
||||
return total
|
||||
|
||||
|
||||
N = 500
|
||||
orders = [2, 3, 5, 10, 20]
|
||||
stats = {}
|
||||
|
||||
ctx = AlgoContext()
|
||||
base = Array.random(N, -1000, 1000, ctx)
|
||||
|
||||
for m in orders:
|
||||
ctx.reset()
|
||||
tree = BTree(m, ctx)
|
||||
for cell in base:
|
||||
tree.insert(cell)
|
||||
stats[m] = {
|
||||
"comparisons": ctx.comparisons,
|
||||
"loads": count_loads(tree.root),
|
||||
"saves": count_saves(tree.root),
|
||||
"height": tree.height(),
|
||||
}
|
||||
|
||||
print(f"{'m':>4} {'Vergleiche':>12} {'Loads':>8} {'Saves':>8} {'Höhe':>6}")
|
||||
for m, s in stats.items():
|
||||
print(f"{m:>4} {s['comparisons']:>12} {s['loads']:>8} {s['saves']:>8} {s['height']:>6}")
|
||||
|
||||
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
|
||||
labels = ["comparisons", "loads", "saves", "height"]
|
||||
titles = ["Vergleiche", "Disk-Loads", "Disk-Saves", "Baumhöhe"]
|
||||
for ax, label, title in zip(axes.flat, labels, titles):
|
||||
ax.plot(orders, [stats[m][label] for m in orders], marker="o")
|
||||
ax.set_xlabel("Ordnung m")
|
||||
ax.set_ylabel(title)
|
||||
ax.set_title(title)
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
# ── Antworten zu den Analysefragen ───────────────────────────────────────────
|
||||
|
||||
# Warum sinkt die Zahl der Disk-Zugriffe mit wachsendem m?
|
||||
# Größeres m → mehr Schlüssel pro Knoten → weniger Knoten insgesamt →
|
||||
# geringere Baumhöhe. Jeder Einfügepfad durchläuft weniger Knoten,
|
||||
# also werden weniger load()/save()-Aufrufe ausgelöst.
|
||||
|
||||
# Warum steigen die internen Vergleiche mit wachsendem m?
|
||||
# Die Suche innerhalb eines Knotens ist immer linear: bis zu 2m-1 Schlüssel
|
||||
# werden verglichen. Die Gesamtkosten pro Einfügung betragen daher
|
||||
# O(m) · O(log_m n) = O(m / log(m) · log n).
|
||||
# Da m/log(m) monoton wächst, steigen die Vergleiche mit jedem größeren m —
|
||||
# nicht ab einem Kipppunkt, sondern kontinuierlich. Das ist der grundlegende
|
||||
# Trade-off: mehr m spart Disk-I/O, kostet aber mehr CPU-Vergleiche.
|
||||
|
||||
# Was bedeutet das für die Wahl von m bei Blockgröße 4 KB?
|
||||
# Ein Knoten enthält 2m-1 Integer-Schlüssel (je 8 Byte) und 2m Kindzeiger.
|
||||
# Die Kindzeiger sind Blocknummern auf dem Speichermedium (je 8 Byte) —
|
||||
# also keine Speicheradressen, sondern Verweise auf andere Disk-Blöcke.
|
||||
# m wird so gewählt, dass ein Knoten genau in einen 4-KB-Block passt:
|
||||
# (2m-1) · 8 + 2m · 8 = 4096
|
||||
# 8 · (4m - 1) = 4096 → m ≈ 128
|
||||
# Disk-I/O-Kosten dominieren gegenüber CPU-Vergleichen → großes m ist optimal.
|
||||
Loading…
x
Reference in New Issue
Block a user