89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
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. |