Musterlösung Praktikum 6
This commit is contained in:
parent
ed329d5d85
commit
779fbab7c8
58
praktika/06_hashtable/aufgabe1_chaining.py
Normal file
58
praktika/06_hashtable/aufgabe1_chaining.py
Normal file
@ -0,0 +1,58 @@
|
||||
import sys, os
|
||||
_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
_l07 = os.path.join(_root, 'vorlesung', 'L07_hashtable')
|
||||
if _l07 not in sys.path: sys.path.insert(0, _l07)
|
||||
if _root not in sys.path: sys.path.insert(0, _root)
|
||||
|
||||
from collections.abc import Callable
|
||||
from utils.algo_context import AlgoContext
|
||||
from utils.algo_int import Int
|
||||
|
||||
|
||||
class HashTableChaining:
|
||||
"""Hashtabelle mit Verkettung (Chaining) zur Kollisionsauflösung."""
|
||||
|
||||
def __init__(self, m: int, h_func: Callable, ctx: AlgoContext):
|
||||
self.ctx = ctx
|
||||
self.m = Int(m, ctx)
|
||||
self.h = h_func
|
||||
self.table = [[] for _ in range(m)]
|
||||
self._n = 0
|
||||
|
||||
def _slot(self, x: Int) -> int:
|
||||
return int(self.h(x, self.m))
|
||||
|
||||
def insert(self, x: Int) -> bool:
|
||||
j = self._slot(x)
|
||||
for elem in self.table[j]:
|
||||
if elem == x: # Duplikat – Vergleich wird gezählt
|
||||
return False
|
||||
self.table[j].append(x)
|
||||
self._n += 1
|
||||
return True
|
||||
|
||||
def search(self, x: Int) -> bool:
|
||||
j = self._slot(x)
|
||||
for elem in self.table[j]:
|
||||
if elem == x: # Vergleich wird gezählt
|
||||
return True
|
||||
return False
|
||||
|
||||
def delete(self, x: Int) -> bool:
|
||||
j = self._slot(x)
|
||||
for i, elem in enumerate(self.table[j]):
|
||||
if elem == x: # Vergleich wird gezählt
|
||||
self.table[j].pop(i)
|
||||
self._n -= 1
|
||||
return True
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
rows = []
|
||||
for i, chain in enumerate(self.table):
|
||||
vals = ' -> '.join(str(e.value) for e in chain) if chain else '—'
|
||||
rows.append(f'[{i:2d}] {vals}')
|
||||
return '\n'.join(rows)
|
||||
|
||||
def alpha(self) -> float:
|
||||
return self._n / int(self.m)
|
||||
49
praktika/06_hashtable/aufgabe2_chaining_test.py
Normal file
49
praktika/06_hashtable/aufgabe2_chaining_test.py
Normal file
@ -0,0 +1,49 @@
|
||||
import sys, os
|
||||
_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
_l07 = os.path.join(_root, 'vorlesung', 'L07_hashtable')
|
||||
_p06 = os.path.dirname(__file__)
|
||||
if _l07 not in sys.path: sys.path.insert(0, _l07)
|
||||
if _root not in sys.path: sys.path.insert(0, _root)
|
||||
if _p06 not in sys.path: sys.path.insert(0, _p06)
|
||||
|
||||
from utils.algo_context import AlgoContext
|
||||
from utils.algo_array import Array
|
||||
from utils.algo_int import Int
|
||||
from analyze_hashtable import h
|
||||
from aufgabe1_chaining import HashTableChaining
|
||||
|
||||
ctx = AlgoContext()
|
||||
values = Array.from_file('data/seq0.txt', ctx)
|
||||
|
||||
ht = HashTableChaining(20, h, ctx)
|
||||
for cell in values:
|
||||
ht.insert(cell)
|
||||
|
||||
# ── a) Belegungsfaktor ───────────────────────────────────────────────────────
|
||||
print("=== a) Nach Einfügen von seq0.txt (14 Werte, m=20) ===")
|
||||
print(ht)
|
||||
print(f"\nBelegungsfaktor α = {ht._n}/{int(ht.m)} = {ht.alpha():.2f}")
|
||||
|
||||
# ── b) Löschen von 52 ────────────────────────────────────────────────────────
|
||||
print("\n=== b) Nach delete(52) ===")
|
||||
ht.delete(Int(52, ctx))
|
||||
print(ht)
|
||||
print(f"Belegungsfaktor α = {ht._n}/{int(ht.m)} = {ht.alpha():.2f}")
|
||||
|
||||
# ── c) Erneut seq0.txt einfügen ──────────────────────────────────────────────
|
||||
print("\n=== c) Erneut seq0.txt einfügen ===")
|
||||
for cell in values:
|
||||
ht.insert(cell)
|
||||
print(f"Belegungsfaktor α = {ht._n}/{int(ht.m)} = {ht.alpha():.2f} (kann > 1 sein!)")
|
||||
print("insert kann bei Verkettung nie False zurückgeben –")
|
||||
print("die Kette wächst unbegrenzt, es gibt kein 'Tabelle voll'.")
|
||||
|
||||
# ── d) Erklärung kein DELETED_MARK ───────────────────────────────────────────
|
||||
print("\n=== d) Warum kein DELETED_MARK? ===")
|
||||
print(
|
||||
"Bei offener Adressierung wird DELETED_MARK benötigt, damit die\n"
|
||||
"Sondierungssequenz bei search nicht vorzeitig abbricht. Bei Verkettung\n"
|
||||
"wird die vollständige Liste eines Slots linear durchsucht – ein gelöschtes\n"
|
||||
"Element wird einfach aus der Liste entfernt. Es gibt keine Sequenz, die\n"
|
||||
"'unterbrochen' werden könnte, daher ist kein Tombstone notwendig."
|
||||
)
|
||||
97
praktika/06_hashtable/aufgabe3_tombstones.py
Normal file
97
praktika/06_hashtable/aufgabe3_tombstones.py
Normal file
@ -0,0 +1,97 @@
|
||||
import sys, os, math
|
||||
_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
_l07 = os.path.join(_root, 'vorlesung', 'L07_hashtable')
|
||||
if _l07 not in sys.path: sys.path.insert(0, _l07)
|
||||
if _root not in sys.path: sys.path.insert(0, _root)
|
||||
|
||||
from utils.algo_context import AlgoContext
|
||||
from utils.algo_array import Array
|
||||
from utils.algo_int import Int
|
||||
from hashtable import HashTableOpenAddressing, DELETED_MARK, UNUSED_MARK
|
||||
from analyze_hashtable import h, f
|
||||
|
||||
ctx = AlgoContext()
|
||||
values = Array.from_file('data/seq0.txt', ctx)
|
||||
|
||||
ht = HashTableOpenAddressing(20, f, ctx)
|
||||
for cell in values:
|
||||
ht.insert(cell)
|
||||
|
||||
# ── a) delete(52) ────────────────────────────────────────────────────────────
|
||||
print("=== a) Nach delete(52) ===")
|
||||
ht.delete(Int(52, ctx))
|
||||
print(ht)
|
||||
|
||||
slot_52 = int(h(Int(52, ctx), ht.m))
|
||||
print(f"\nSlot h(52) = {slot_52}: Inhalt = {ht.table[Int(slot_52, ctx)].value!r} ← DELETED_MARK")
|
||||
|
||||
# ── b) Warum search bei DELETED_MARK weitersucht ─────────────────────────────
|
||||
print("\n=== b) Warum search bei DELETED_MARK fortgesetzt wird ===")
|
||||
print(
|
||||
"search prüft in jeder Iteration: Ist der aktuelle Slot UNUSED? → Abbruch\n"
|
||||
"(der gesuchte Wert wurde nie an eine spätere Position sondiert, also ist\n"
|
||||
"er nicht vorhanden). Ist der Slot DELETED_MARK? → weitersuchen, denn der\n"
|
||||
"Wert könnte durch einen früheren Eintrag über diesen Slot hinaus sondiert\n"
|
||||
"worden sein. DELETED_MARK erhält die Sondierungssequenz aufrecht."
|
||||
)
|
||||
|
||||
# ── c) Konkretes Gegenbeispiel: UNUSED_MARK statt DELETED_MARK ───────────────
|
||||
print("\n=== c) Konkretes Gegenbeispiel ===")
|
||||
# Wir bauen eine kleine Tabelle, in der das Problem klar sichtbar ist.
|
||||
ctx2 = AlgoContext()
|
||||
ht2 = HashTableOpenAddressing(20, f, ctx2)
|
||||
for cell in values:
|
||||
ht2.insert(cell)
|
||||
|
||||
# Finde einen Wert, der über denselben Slot wie 52 sondiert
|
||||
# h(52) = slot_52; suche einen anderen Wert v mit h(v)=slot_52 oder f(v,0)=slot_52
|
||||
slot_52_v = int(h(Int(52, ctx2), ht2.m))
|
||||
print(f"h(52) = {slot_52_v}")
|
||||
|
||||
# Prüfe: gibt es einen Wert im Baum, der bei i=0 auf slot_52 trifft?
|
||||
# Sondierungssequenz für jeden eingefügten Wert anzeigen
|
||||
from utils.algo_int import Int as I
|
||||
_A = (math.sqrt(5) - 1) / 2
|
||||
def h_raw(v, m=20):
|
||||
full = v * _A
|
||||
return int(abs(full - int(full)) * m)
|
||||
|
||||
def f_raw(v, i, m=20):
|
||||
return (h_raw(v, m) + i + 5*i*i) % m
|
||||
|
||||
print("\nSondierungssequenzen (erste 3 Schritte) für seq0.txt-Werte:")
|
||||
raw_vals = [int(str(c)) for c in values]
|
||||
for v in raw_vals:
|
||||
seq = [f_raw(v, i) for i in range(3)]
|
||||
print(f" h({v:4d}) = {h_raw(v):2d}, f(·,0)={seq[0]:2d}, f(·,1)={seq[1]:2d}, f(·,2)={seq[2]:2d}")
|
||||
|
||||
# 52 belegt slot_52; angenommen 58 kollidiert und liegt hinter 52
|
||||
# → delete(52) mit UNUSED würde search(58) frühzeitig abbrechen
|
||||
print(
|
||||
"\nAngenommen, zwei Werte v1 und v2 sondieren über denselben Slot:\n"
|
||||
" v1 landet in Slot s (erste Sondierung)\n"
|
||||
" v2 kollidiert bei Slot s und landet bei s' (zweite Sondierung)\n"
|
||||
"Wird v1 gelöscht und Slot s auf UNUSED gesetzt, bricht search(v2)\n"
|
||||
"bei Slot s ab – obwohl v2 noch in s' liegt. Das Ergebnis ist 'nicht\n"
|
||||
"gefunden', obwohl v2 vorhanden ist. DELETED_MARK verhindert das."
|
||||
)
|
||||
|
||||
# ── d) Wo landet 24 nach delete(52)? ─────────────────────────────────────────
|
||||
print("\n=== d) Wo landet 24 nach delete(52)? ===")
|
||||
h24 = h_raw(24)
|
||||
print(f"h(24) = int({{24·A}} mod 1 · 20) = {h24}")
|
||||
for i in range(5):
|
||||
slot = f_raw(24, i)
|
||||
val = ht.table[Int(slot, ctx)].value
|
||||
status = "frei (DELETED/UNUSED)" if val in (DELETED_MARK, UNUSED_MARK) else f"belegt ({val})"
|
||||
print(f"f(24, {i}, 20) = {slot:2d} → {status}")
|
||||
if val in (DELETED_MARK, UNUSED_MARK):
|
||||
print(f" ⇒ 24 landet in Slot {slot}")
|
||||
break
|
||||
|
||||
ht.insert(Int(24, ctx))
|
||||
print(f"\nVerifikation __str__ (relevante Slots):")
|
||||
for idx in [0, 1, 2, 3, 16, 17]:
|
||||
v = ht.table[Int(idx, ctx)].value
|
||||
marker = " ← 24 eingefügt" if str(v) == "24" else ""
|
||||
print(f" Slot {idx:2d}: {v}{marker}")
|
||||
70
praktika/06_hashtable/aufgabe4_kapazitaet.py
Normal file
70
praktika/06_hashtable/aufgabe4_kapazitaet.py
Normal file
@ -0,0 +1,70 @@
|
||||
import sys, os
|
||||
_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
_l07 = os.path.join(_root, 'vorlesung', 'L07_hashtable')
|
||||
_p06 = os.path.dirname(__file__)
|
||||
if _l07 not in sys.path: sys.path.insert(0, _l07)
|
||||
if _root not in sys.path: sys.path.insert(0, _root)
|
||||
if _p06 not in sys.path: sys.path.insert(0, _p06)
|
||||
|
||||
from utils.algo_context import AlgoContext
|
||||
from utils.algo_array import Array
|
||||
from hashtable import HashTableOpenAddressing
|
||||
from analyze_hashtable import h, f
|
||||
from aufgabe1_chaining import HashTableChaining
|
||||
|
||||
ctx = AlgoContext()
|
||||
values = Array.from_file('data/seq1.txt', ctx)
|
||||
n = len(values)
|
||||
|
||||
# ── a) Größe 90 ───────────────────────────────────────────────────────────────
|
||||
ht90 = HashTableOpenAddressing(90, f, ctx)
|
||||
inserted90 = sum(1 for cell in values if ht90.insert(cell))
|
||||
rejected90 = n - inserted90
|
||||
print("=== a) HashTableOpenAddressing m=90, seq1.txt (100 Werte) ===")
|
||||
print(f"Eingefügt: {inserted90}, Abgewiesen: {rejected90}, "
|
||||
f"Freie Slots: {90 - inserted90}")
|
||||
print(
|
||||
"Ursache: Die quadratische Sondierungsfunktion f(x,i) = (h(x)+i+5i²) mod m\n"
|
||||
"erzeugt für m=90 (keine Primzahl) zyklische Sondierungssequenzen, die nicht\n"
|
||||
"alle 90 Slots abdecken. Sobald alle erreichbaren Slots belegt sind, schlägt\n"
|
||||
"insert fehl – auch wenn noch andere Slots frei sind."
|
||||
)
|
||||
|
||||
# ── b) Größe 89 ───────────────────────────────────────────────────────────────
|
||||
ctx2 = AlgoContext()
|
||||
values2 = Array.from_file('data/seq1.txt', ctx2)
|
||||
ht89 = HashTableOpenAddressing(89, f, ctx2)
|
||||
inserted89 = sum(1 for cell in values2 if ht89.insert(cell))
|
||||
rejected89 = n - inserted89
|
||||
print(f"\n=== b) HashTableOpenAddressing m=89 (Primzahl), seq1.txt ===")
|
||||
print(f"Eingefügt: {inserted89}, Abgewiesen: {rejected89}")
|
||||
print(
|
||||
"Bei Primzahlgröße garantiert die quadratische Sondierung, dass mindestens\n"
|
||||
"m/2 verschiedene Slots erreicht werden (bei geeigneten Konstanten c1, c2\n"
|
||||
"sogar alle m). Für m=89 ist die Sondierungssequenz deutlich länger, bevor\n"
|
||||
"sie sich wiederholt – dadurch können mehr Werte platziert werden."
|
||||
)
|
||||
|
||||
# ── c) HashTableChaining m=20, seq1.txt ───────────────────────────────────────
|
||||
ctx3 = AlgoContext()
|
||||
values3 = Array.from_file('data/seq1.txt', ctx3)
|
||||
htc = HashTableChaining(20, h, ctx3)
|
||||
for cell in values3:
|
||||
htc.insert(cell)
|
||||
print(f"\n=== c) HashTableChaining m=20, seq1.txt ===")
|
||||
unique = len(set(int(str(c)) for c in values3))
|
||||
print(f"Eingefügt: {htc._n} (seq1.txt hat {n} Werte, davon {unique} eindeutig; "
|
||||
f"Duplikate werden nicht doppelt gespeichert)")
|
||||
print(f"Belegungsfaktor α = {htc._n}/{int(htc.m)} = {htc.alpha():.2f}")
|
||||
print("Verkettung hat keine Kapazitätsgrenze – die Ketten wachsen unbegrenzt.")
|
||||
|
||||
# ── d) Theoretische Sondierungsanzahl ─────────────────────────────────────────
|
||||
print("\n=== d) Mittlere Sondierungsanzahl 1/(1-α) ===")
|
||||
for alpha in [0.5, 0.9]:
|
||||
probes = 1 / (1 - alpha)
|
||||
print(f" α = {alpha}: 1/(1−{alpha}) = {probes:.1f} Sondierungen")
|
||||
print(
|
||||
"Bis α ≈ 0,7 bleibt die offene Adressierung praktikabel (≈ 3,3 Sondierungen).\n"
|
||||
"Für α > 0,8 steigen die Kosten rapide; α = 0,9 bedeutet bereits 10 Sondierungen\n"
|
||||
"im Schnitt. Als Faustregel gilt: Tabelle vergrößern (Rehashing), wenn α > 0,75."
|
||||
)
|
||||
106
praktika/06_hashtable/aufgabe5_vergleich.py
Normal file
106
praktika/06_hashtable/aufgabe5_vergleich.py
Normal file
@ -0,0 +1,106 @@
|
||||
import sys, os, math
|
||||
_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
_l07 = os.path.join(_root, 'vorlesung', 'L07_hashtable')
|
||||
_p06 = os.path.dirname(__file__)
|
||||
if _l07 not in sys.path: sys.path.insert(0, _l07)
|
||||
if _root not in sys.path: sys.path.insert(0, _root)
|
||||
if _p06 not in sys.path: sys.path.insert(0, _p06)
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
from utils.algo_context import AlgoContext
|
||||
from utils.algo_array import Array
|
||||
from hashtable import HashTableOpenAddressing
|
||||
from analyze_hashtable import h, f
|
||||
from aufgabe1_chaining import HashTableChaining
|
||||
|
||||
|
||||
def measure(m: int, target_alpha: float, seed: int = 42):
|
||||
"""Befüllt beide Tabellentypen bis Ziel-α, misst Vergleiche beim Suchen."""
|
||||
n = int(m * target_alpha)
|
||||
|
||||
import random
|
||||
random.seed(seed)
|
||||
|
||||
ctx_oa = AlgoContext()
|
||||
vals_oa = Array.random(n, -10000, 10000, ctx_oa)
|
||||
ht_oa = HashTableOpenAddressing(m, f, ctx_oa)
|
||||
inserted = []
|
||||
for cell in vals_oa:
|
||||
if ht_oa.insert(cell):
|
||||
inserted.append(cell)
|
||||
|
||||
ctx_oa.reset()
|
||||
for cell in inserted:
|
||||
ht_oa.search(cell)
|
||||
cmp_oa = ctx_oa.comparisons
|
||||
|
||||
ctx_ch = AlgoContext()
|
||||
vals_ch = Array.random(n, -10000, 10000, ctx_ch)
|
||||
ht_ch = HashTableChaining(m, h, ctx_ch)
|
||||
for cell in vals_ch:
|
||||
ht_ch.insert(cell)
|
||||
|
||||
ctx_ch.reset()
|
||||
for i, chain in enumerate(ht_ch.table):
|
||||
for elem in chain:
|
||||
ht_ch.search(elem)
|
||||
cmp_ch = ctx_ch.comparisons
|
||||
|
||||
return cmp_oa, cmp_ch
|
||||
|
||||
|
||||
sizes = [50, 100, 200, 500, 1000]
|
||||
|
||||
for target_alpha, label in [(0.7, "α ≈ 0,7"), (0.9, "α ≈ 0,9")]:
|
||||
oa_vals, ch_vals = [], []
|
||||
for m in sizes:
|
||||
coa, cch = measure(m, target_alpha)
|
||||
oa_vals.append(coa)
|
||||
ch_vals.append(cch)
|
||||
print(f"\n{'m':>6} {'OA Vergl.':>12} {'Chaining Vergl.':>16} ({label})")
|
||||
for m, co, cc in zip(sizes, oa_vals, ch_vals):
|
||||
print(f"{m:>6} {co:>12} {cc:>16}")
|
||||
|
||||
# ── Plot ──────────────────────────────────────────────────────────────────────
|
||||
fig, axes = plt.subplots(1, 2, figsize=(11, 4))
|
||||
for ax, target_alpha, label in zip(axes, [0.7, 0.9], ["α ≈ 0,7", "α ≈ 0,9"]):
|
||||
oa_vals, ch_vals = [], []
|
||||
for m in sizes:
|
||||
coa, cch = measure(m, target_alpha)
|
||||
oa_vals.append(coa)
|
||||
ch_vals.append(cch)
|
||||
ax.plot(sizes, oa_vals, marker='o', label='Offene Adressierung')
|
||||
ax.plot(sizes, ch_vals, marker='s', label='Verkettung')
|
||||
ax.set_xlabel('Tabellengröße m')
|
||||
ax.set_ylabel('Vergleiche (Suchen aller Werte)')
|
||||
ax.set_title(label)
|
||||
ax.legend()
|
||||
plt.tight_layout()
|
||||
plt.savefig('vergleich_strategien.pdf')
|
||||
plt.show()
|
||||
|
||||
# ── Antworten ─────────────────────────────────────────────────────────────────
|
||||
print("\n=== a) Welche Strategie benötigt mehr Vergleiche? ===")
|
||||
print(
|
||||
"Bei gleichem α erzeugt die offene Adressierung mehr Vergleiche:\n"
|
||||
"Kollisionen führen zu langen Sondierungssequenzen, bei denen jeder\n"
|
||||
"besuchte Slot einen Vergleich kostet. Die Verkettung sucht nur in der\n"
|
||||
"jeweiligen Kette (im Schnitt α/2 Vergleiche pro Suche)."
|
||||
)
|
||||
|
||||
print("\n=== b) α = 0,9 ===")
|
||||
print(
|
||||
"Die offene Adressierung verschlechtert sich drastisch: 1/(1−0,9) = 10\n"
|
||||
"Sondierungen im Schnitt – das ist im Plot klar erkennbar. Bei der\n"
|
||||
"Verkettung wächst die mittlere Kettenlänge auf α = 0,9, also ca. 1 Extra-\n"
|
||||
"Vergleich. Der Nachteil der offenen Adressierung wird bei hohem α dominant."
|
||||
)
|
||||
|
||||
print("\n=== c) Systemvorteile ===")
|
||||
print(
|
||||
"Verkettung: Keine Kapazitätsgrenze; einfacheres Löschen (kein\n"
|
||||
" Tombstone); gut parallelisierbar (eine Kette pro Slot).\n"
|
||||
"Offene Adressierung: Besseres Cache-Verhalten (alle Einträge im\n"
|
||||
" zusammenhängenden Array, keine Pointer-Indirektion);\n"
|
||||
" geringerer Speicherverbrauch (keine Listenknoten)."
|
||||
)
|
||||
Loading…
x
Reference in New Issue
Block a user