Compare commits
20
Commits
4f5a78ac05
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cae3355bf1 | ||
|
|
1692129d89 | ||
|
|
586a52f9a5 | ||
|
|
c478317d76 | ||
|
|
646b9c6881 | ||
|
|
779fbab7c8 | ||
|
|
ed329d5d85 | ||
|
|
5aca875fa6 | ||
|
|
064d392d98 | ||
|
|
a553d7fd01 | ||
|
|
24d15e1e44 | ||
|
|
f0b82e726a | ||
|
|
7352149e2e | ||
|
|
88acfb9554 | ||
|
|
47d1217dcb | ||
|
|
57476c0a4b | ||
|
|
fe533e6d4a | ||
|
|
1dfdff9391 | ||
|
|
c48b5c7e59 | ||
|
|
228273f399 |
No files matched your search
+26
@@ -0,0 +1,26 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Virtual environment
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Claude Code metadata
|
||||||
|
CLAUDE.md
|
||||||
|
.claude/
|
||||||
|
specs/
|
||||||
|
docs/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
/.agents/
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
# Algorithmen und Datenstrukturen – SoSe 26
|
||||||
|
|
||||||
|
Dieses Repository enthält den Code zur Vorlesung. Neben den Vorlesungsbeispielen
|
||||||
|
bietet es ein kleines Framework, mit dem ihr eure eigenen Algorithmen
|
||||||
|
**automatisch auf Operationen messen** könnt – ohne den Algorithmus selbst
|
||||||
|
anfassen zu müssen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wozu das Framework?
|
||||||
|
|
||||||
|
In der Vorlesung analysieren wir Algorithmen theoretisch mit der O-Notation.
|
||||||
|
Das Framework erlaubt euch, diese Theorie **empirisch zu überprüfen**: Ihr
|
||||||
|
implementiert euren Algorithmus genauso wie in Python üblich – nur dass ihr
|
||||||
|
statt `list` und `int` die Wrapper-Typen `Array` und `Int` verwendet. Das
|
||||||
|
Framework zählt dann im Hintergrund automatisch Vergleiche, Lese- und
|
||||||
|
Schreibzugriffe sowie arithmetische Operationen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Schnellstart
|
||||||
|
|
||||||
|
### 1. Kontext anlegen
|
||||||
|
|
||||||
|
```python
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_array import Array
|
||||||
|
from utils.algo_range import Range
|
||||||
|
|
||||||
|
ctx = AlgoContext()
|
||||||
|
```
|
||||||
|
|
||||||
|
`AlgoContext` ist der Zähler. Ihr legt ihn einmal an und gebt ihn an eure
|
||||||
|
Datenstrukturen weiter.
|
||||||
|
|
||||||
|
### 2. Array befüllen
|
||||||
|
|
||||||
|
```python
|
||||||
|
z = Array.random(10, -100, 100, ctx) # 10 Zufallszahlen zwischen -100 und 100
|
||||||
|
# oder:
|
||||||
|
z = Array([5, 3, 8, 1, 9, 2], ctx) # eigene Werte
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Algorithmus schreiben
|
||||||
|
|
||||||
|
Schreibt euren Algorithmus wie gewohnt. `Range` ist ein Drop-in-Ersatz für
|
||||||
|
`range` und liefert ebenfalls `Int`-Objekte – allerdings ohne Verbindung zum
|
||||||
|
Zähler. Dadurch wird **Indexarithmetik** (`j - 1`, `i + 1`) nicht mitgezählt,
|
||||||
|
während Operationen auf Array-*Inhalten* (die ja den echten `ctx` tragen) weiterhin
|
||||||
|
erfasst werden. Das entspricht der üblichen Konvention: gezählt werden nur
|
||||||
|
Operationen auf den Daten, nicht die Schleifensteuerung.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def insertion_sort(z: Array, ctx: AlgoContext):
|
||||||
|
for i in Range(1, len(z)):
|
||||||
|
elem = z[i] # liest z[i] (1 Lesezugriff)
|
||||||
|
j = int(i) - 1
|
||||||
|
while j >= 0 and z[j] > elem: # vergleicht (1 Vergleich pro Schritt)
|
||||||
|
z[j + 1] = z[j] # schreibt (1 Schreibzugriff)
|
||||||
|
j -= 1
|
||||||
|
z[j + 1] = elem
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Ausführen und Ergebnis ablesen
|
||||||
|
|
||||||
|
```python
|
||||||
|
insertion_sort(z, ctx)
|
||||||
|
|
||||||
|
print(ctx.comparisons) # Anzahl Vergleiche
|
||||||
|
print(ctx.reads) # Lesezugriffe
|
||||||
|
print(ctx.writes) # Schreibzugriffe
|
||||||
|
print(ctx.additions) # Additionen
|
||||||
|
```
|
||||||
|
|
||||||
|
Oder kompakt:
|
||||||
|
|
||||||
|
```python
|
||||||
|
print(ctx.summary())
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Komplexität über mehrere Eingabegrößen plotten
|
||||||
|
|
||||||
|
```python
|
||||||
|
def analyze(sort_func, sizes):
|
||||||
|
ctx = AlgoContext()
|
||||||
|
for size in sizes:
|
||||||
|
ctx.reset()
|
||||||
|
z = Array.random(size, -100, 100, ctx)
|
||||||
|
sort_func(z, ctx)
|
||||||
|
ctx.save_stats(size)
|
||||||
|
ctx.plot_stats(["comparisons", "writes"])
|
||||||
|
|
||||||
|
analyze(insertion_sort, range(10, 201, 10))
|
||||||
|
```
|
||||||
|
|
||||||
|
Das öffnet ein Matplotlib-Fenster mit dem Verlauf der gezählten Operationen
|
||||||
|
über die Eingabegröße – ihr seht direkt, ob euer Algorithmus z.B. quadratisch oder
|
||||||
|
linear wächst.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verfügbare Zähler
|
||||||
|
|
||||||
|
| Attribut | Was wird gezählt |
|
||||||
|
|-----------------|-------------------------------------------|
|
||||||
|
| `comparisons` | `<`, `>`, `<=`, `>=`, `==`, `!=` |
|
||||||
|
| `reads` | Lesezugriffe auf `Int`-Werte |
|
||||||
|
| `writes` | Schreibzugriffe (Zuweisung, `swap`) |
|
||||||
|
| `additions` | `+`, `+=` |
|
||||||
|
| `subtractions` | `-`, `-=` |
|
||||||
|
| `multiplications` | `*`, `*=` |
|
||||||
|
| `divisions` | `/`, `//`, `/=` |
|
||||||
|
| `bitops` | `&`, `\|`, `^`, `<<`, `>>` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Struktur des Repositories
|
||||||
|
|
||||||
|
```
|
||||||
|
utils/ Framework-Dateien (AlgoContext, Int, Array, Range)
|
||||||
|
vorlesung/ Vorlesungsbeispiele, geordnet nach Lektionsnummer
|
||||||
|
praktika/ Aufgabenstellungen der Praktika
|
||||||
|
playground/ Eure eigenen Abgaben und Experimente
|
||||||
|
data/ Beispieldatensätze
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Vorlesungsbeispiele in `vorlesung/` sind vollständig mit dem Framework
|
||||||
|
instrumentiert und können als Vorlage für eigene Implementierungen dienen.
|
||||||
|
|
||||||
|
## Datenpfade
|
||||||
|
|
||||||
|
Die Demodaten in `data/` werden über `path()` aus `utils.algo_path` adressiert.
|
||||||
|
Die Funktion liefert immer den absoluten Pfad relativ zum Projektverzeichnis –
|
||||||
|
unabhängig davon, aus welchem Verzeichnis ihr das Skript startet oder welche IDE
|
||||||
|
ihr verwendet:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from utils.algo_path import path
|
||||||
|
|
||||||
|
z = Array.from_file(path("data/seq0.txt"), ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
## PriorityQueue
|
||||||
|
|
||||||
|
Für Graphalgorithmen (z.B. Dijkstra) steht eine fertige `PriorityQueue` bereit.
|
||||||
|
Sie basiert intern auf einem Heap – das Prinzip habt ihr in der Vorlesung zu
|
||||||
|
Heap Sort kennengelernt. Als Abkürzung dürft ihr die fertige Klasse verwenden:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from utils.algo_priority_queue import PriorityQueue
|
||||||
|
|
||||||
|
pq = PriorityQueue()
|
||||||
|
pq.add_or_update("A", 0) # Knoten mit Priorität (Distanz) eintragen
|
||||||
|
pq.add_or_update("B", 5)
|
||||||
|
pq.add_or_update("B", 2) # Priorität aktualisieren
|
||||||
|
node, dist = pq.pop() # liefert ("A", 0)
|
||||||
|
```
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_array import Array
|
||||||
|
from utils.algo_int import Int
|
||||||
|
from utils.algo_range import Range
|
||||||
|
|
||||||
|
|
||||||
|
def max_sequence_1(z: Array, ctx: AlgoContext):
|
||||||
|
"""O(n³) – dreifach verschachtelte Schleife."""
|
||||||
|
n = len(z)
|
||||||
|
m = Int(float('-inf'), ctx)
|
||||||
|
s = Int(0, ctx)
|
||||||
|
l = Int(0, ctx)
|
||||||
|
r = Int(0, ctx)
|
||||||
|
|
||||||
|
for i in Range(n):
|
||||||
|
for j in Range(int(i), n):
|
||||||
|
s.set(0)
|
||||||
|
for k in Range(int(i), int(j)):
|
||||||
|
s += z[k]
|
||||||
|
if s > m:
|
||||||
|
m.set(s)
|
||||||
|
l.set(Int(int(i), ctx))
|
||||||
|
r.set(Int(int(j), ctx))
|
||||||
|
return m, l, r
|
||||||
|
|
||||||
|
|
||||||
|
def max_sequence_2(z: Array, ctx: AlgoContext):
|
||||||
|
"""O(n²) – doppelt verschachtelte Schleife."""
|
||||||
|
n = len(z)
|
||||||
|
m = Int(float('-inf'), ctx)
|
||||||
|
s = Int(0, ctx)
|
||||||
|
left = Int(0, ctx)
|
||||||
|
right = Int(0, ctx)
|
||||||
|
|
||||||
|
for i in Range(n):
|
||||||
|
s.set(0)
|
||||||
|
for j in Range(int(i), n):
|
||||||
|
s += z[j]
|
||||||
|
if s > m:
|
||||||
|
m.set(s)
|
||||||
|
left.set(Int(int(i), ctx))
|
||||||
|
right.set(Int(int(j), ctx))
|
||||||
|
return m, left, right
|
||||||
|
|
||||||
|
|
||||||
|
def _finde_zwischen(z: Array, ctx: AlgoContext, l: int, m: int, r: int):
|
||||||
|
"""Hilfsfunktion für max_sequence_3: findet die maximale Teilsumme,
|
||||||
|
die die Mitte m überschreitet (3. Fall von Teile-und-Herrsche)."""
|
||||||
|
links_max = Int(float('-inf'), ctx)
|
||||||
|
s = Int(0, ctx)
|
||||||
|
links = Int(l, ctx)
|
||||||
|
for i in Range(m, l - 1, -1):
|
||||||
|
s += z[i]
|
||||||
|
if s > links_max:
|
||||||
|
links_max.set(s)
|
||||||
|
links.set(Int(int(i), ctx))
|
||||||
|
|
||||||
|
rechts_max = Int(float('-inf'), ctx)
|
||||||
|
s.set(0)
|
||||||
|
rechts = Int(m + 1, ctx)
|
||||||
|
for i in Range(m + 1, r + 1):
|
||||||
|
s += z[i]
|
||||||
|
if s > rechts_max:
|
||||||
|
rechts_max.set(s)
|
||||||
|
rechts.set(Int(int(i), ctx))
|
||||||
|
|
||||||
|
return links_max + rechts_max, links, rechts
|
||||||
|
|
||||||
|
|
||||||
|
def max_sequence_3(z: Array, ctx: AlgoContext, l: int = None, r: int = None):
|
||||||
|
"""O(n ld n) – Teile-und-Herrsche."""
|
||||||
|
if l is None:
|
||||||
|
l = 0
|
||||||
|
if r is None:
|
||||||
|
r = len(z) - 1
|
||||||
|
|
||||||
|
if l == r:
|
||||||
|
return z[l], Int(l, ctx), Int(r, ctx)
|
||||||
|
|
||||||
|
m = (l + r) // 2
|
||||||
|
links_max, links_l, links_r = max_sequence_3(z, ctx, l, m)
|
||||||
|
rechts_max, rechts_l, rechts_r = max_sequence_3(z, ctx, m + 1, r)
|
||||||
|
zwi_max, zwi_l, zwi_r = _finde_zwischen(z, ctx, l, m, r)
|
||||||
|
|
||||||
|
if links_max >= rechts_max and links_max >= zwi_max:
|
||||||
|
return links_max, links_l, links_r
|
||||||
|
if rechts_max >= links_max and rechts_max >= zwi_max:
|
||||||
|
return rechts_max, rechts_l, rechts_r
|
||||||
|
return zwi_max, zwi_l, zwi_r
|
||||||
|
|
||||||
|
|
||||||
|
def max_sequence_4(z: Array, ctx: AlgoContext):
|
||||||
|
"""O(n) – Kadane's Algorithmus."""
|
||||||
|
n = len(z)
|
||||||
|
m = Int(float('-inf'), ctx)
|
||||||
|
cur_sum = Int(0, ctx)
|
||||||
|
cur_left = Int(0, ctx)
|
||||||
|
left = Int(0, ctx)
|
||||||
|
right = Int(0, ctx)
|
||||||
|
|
||||||
|
for i in Range(n):
|
||||||
|
cur_sum += z[i]
|
||||||
|
if cur_sum > m:
|
||||||
|
m.set(cur_sum)
|
||||||
|
left.set(cur_left)
|
||||||
|
right.set(Int(int(i), ctx))
|
||||||
|
if cur_sum < 0:
|
||||||
|
cur_sum.set(0)
|
||||||
|
cur_left.set(Int(int(i) + 1, ctx))
|
||||||
|
|
||||||
|
return m, left, right
|
||||||
|
|
||||||
|
|
||||||
|
def example(max_sequence_func):
|
||||||
|
ctx = AlgoContext()
|
||||||
|
data = [-59, 52, 46, 14, -50, 58, -87, -77, 34, 15]
|
||||||
|
print(data)
|
||||||
|
z = Array(data, ctx)
|
||||||
|
m, l, r = max_sequence_func(z, ctx)
|
||||||
|
print(m, l, r)
|
||||||
|
|
||||||
|
|
||||||
|
def seq(filename, max_sequence_func):
|
||||||
|
ctx = AlgoContext()
|
||||||
|
z = Array.from_file(filename, ctx)
|
||||||
|
m, l, r = max_sequence_func(z, ctx)
|
||||||
|
print(m, l, r)
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_complexity(max_sequence_func, sizes):
|
||||||
|
ctx = AlgoContext()
|
||||||
|
for size in sizes:
|
||||||
|
ctx.reset()
|
||||||
|
z = Array.random(size, -100, 100, ctx)
|
||||||
|
max_sequence_func(z, ctx)
|
||||||
|
ctx.save_stats(size)
|
||||||
|
|
||||||
|
ctx.plot_stats(["additions"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
example(max_sequence_3)
|
||||||
|
for filename in ["data/seq0.txt", "data/seq1.txt"]:
|
||||||
|
print(filename)
|
||||||
|
seq(filename, max_sequence_3)
|
||||||
|
analyze_complexity(max_sequence_3, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_array import Array
|
||||||
|
from vorlesung.L03_fortgeschrittenes_sortieren.quick_sorting import quick_sort
|
||||||
|
from vorlesung.L03_fortgeschrittenes_sortieren.heap_sorting import heap_sort
|
||||||
|
from merge_sorting import merge_sort
|
||||||
|
|
||||||
|
SIZES = list(range(10, 210, 10))
|
||||||
|
|
||||||
|
algorithms = [
|
||||||
|
("Quicksort", quick_sort),
|
||||||
|
("Heapsort", heap_sort),
|
||||||
|
("Merge Sort", merge_sort),
|
||||||
|
]
|
||||||
|
|
||||||
|
comparisons = {name: [] for name, _ in algorithms}
|
||||||
|
writes = {name: [] for name, _ in algorithms}
|
||||||
|
|
||||||
|
for name, sort_func in algorithms:
|
||||||
|
ctx = AlgoContext()
|
||||||
|
for n in SIZES:
|
||||||
|
ctx.reset()
|
||||||
|
z = Array.random(n, -1000, 1000, ctx)
|
||||||
|
sort_func(z, ctx)
|
||||||
|
comparisons[name].append(ctx.comparisons)
|
||||||
|
writes[name].append(ctx.writes)
|
||||||
|
|
||||||
|
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
|
||||||
|
|
||||||
|
for name in comparisons:
|
||||||
|
ax1.plot(SIZES, comparisons[name], label=name, marker='o', markersize=3)
|
||||||
|
ax1.set_ylabel("Vergleiche")
|
||||||
|
ax1.set_title("Quicksort vs. Heapsort vs. Merge Sort")
|
||||||
|
ax1.legend()
|
||||||
|
|
||||||
|
for name in writes:
|
||||||
|
ax2.plot(SIZES, writes[name], label=name, marker='o', markersize=3)
|
||||||
|
ax2.set_ylabel("Schreibzugriffe")
|
||||||
|
ax2.set_xlabel("n")
|
||||||
|
ax2.legend()
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.show()
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import random
|
||||||
|
import pygame
|
||||||
|
from utils.algo_game import Game
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_array import Array
|
||||||
|
from merge_sorting import merge_sort_stepwise
|
||||||
|
|
||||||
|
WHITE = (255, 255, 255)
|
||||||
|
BLUE = (0, 0, 255)
|
||||||
|
|
||||||
|
class MergeGame(Game):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("Merge Game", fps=10, size=(400, 400))
|
||||||
|
random.seed()
|
||||||
|
l = list(range(1, 101))
|
||||||
|
random.shuffle(l)
|
||||||
|
self.ctx = AlgoContext()
|
||||||
|
self.z = Array(l, self.ctx)
|
||||||
|
self.finished = False
|
||||||
|
self.sort_generator = merge_sort_stepwise(self.z, self.ctx)
|
||||||
|
|
||||||
|
def update_game(self):
|
||||||
|
if not self.finished:
|
||||||
|
try:
|
||||||
|
next(self.sort_generator)
|
||||||
|
except StopIteration:
|
||||||
|
self.finished = True
|
||||||
|
return True
|
||||||
|
|
||||||
|
def draw_game(self):
|
||||||
|
self.screen.fill(WHITE)
|
||||||
|
for i, cell in enumerate(self.z):
|
||||||
|
x = 50 + i*3
|
||||||
|
y = 350 - cell.value * 3
|
||||||
|
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
||||||
|
super().draw_game()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
s = MergeGame()
|
||||||
|
s.run()
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
from utils.algo_array import Array
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_range import Range
|
||||||
|
from time import perf_counter as pfc
|
||||||
|
|
||||||
|
|
||||||
|
def merge_sort_stepwise(z: Array, ctx: AlgoContext, l=None, r=None, buffer=None):
|
||||||
|
if l is None:
|
||||||
|
l = 0
|
||||||
|
if r is None:
|
||||||
|
r = len(z) - 1
|
||||||
|
if buffer is None:
|
||||||
|
buffer = Array([0] * len(z), ctx)
|
||||||
|
if l < r:
|
||||||
|
m = (l + r) // 2
|
||||||
|
yield from merge_sort_stepwise(z, ctx, l, m, buffer)
|
||||||
|
yield from merge_sort_stepwise(z, ctx, m + 1, r, buffer)
|
||||||
|
yield from merge(z, l, m, r, buffer)
|
||||||
|
|
||||||
|
def merge(z: Array, l: int, m: int, r: int, buffer: Array):
|
||||||
|
i, j, k = l, m + 1, l
|
||||||
|
while i <= m and j <= r:
|
||||||
|
if z[i] <= z[j]:
|
||||||
|
buffer[k] = z[i]
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
buffer[k] = z[j]
|
||||||
|
j += 1
|
||||||
|
k += 1
|
||||||
|
while i <= m:
|
||||||
|
buffer[k] = z[i]
|
||||||
|
i += 1
|
||||||
|
k += 1
|
||||||
|
while j <= r:
|
||||||
|
buffer[k] = z[j]
|
||||||
|
j += 1
|
||||||
|
k += 1
|
||||||
|
for k in Range(l, r + 1):
|
||||||
|
z[k] = buffer[k]
|
||||||
|
yield z
|
||||||
|
|
||||||
|
def merge_sort(z: Array, ctx: AlgoContext):
|
||||||
|
for _ in merge_sort_stepwise(z, ctx):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sort_file(filename, sort_func):
|
||||||
|
ctx = AlgoContext()
|
||||||
|
z = Array.from_file(filename, ctx)
|
||||||
|
sort_func(z, ctx)
|
||||||
|
return z
|
||||||
|
|
||||||
|
def analyze_complexity(sort_func, sizes, presorted=False):
|
||||||
|
ctx = AlgoContext()
|
||||||
|
for size in sizes:
|
||||||
|
ctx.reset()
|
||||||
|
if presorted:
|
||||||
|
z = Array.sorted(size, ctx)
|
||||||
|
else:
|
||||||
|
z = Array.random(size, -100, 100, ctx)
|
||||||
|
sort_func(z, ctx)
|
||||||
|
ctx.save_stats(size)
|
||||||
|
|
||||||
|
ctx.plot_stats(["comparisons", "writes"])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
analyze_complexity(merge_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
||||||
|
for filename in ["data/seq0.txt", "data/seq1.txt", "data/seq2.txt", "data/seq3.txt"]:
|
||||||
|
print(filename)
|
||||||
|
start = pfc()
|
||||||
|
print(sort_file(filename, merge_sort))
|
||||||
|
print(f"Dauer: {pfc() - start:.4f}s")
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
|
||||||
|
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_int import Int
|
||||||
|
|
||||||
|
|
||||||
|
class PriorityQueue:
|
||||||
|
"""Max-Heap-basierte Vorrang-Warteschlange mit Operationszählung.
|
||||||
|
|
||||||
|
Höhere Prioritätswerte werden zuerst entnommen. Prioritäten werden als
|
||||||
|
Int-Objekte gespeichert, sodass Vergleiche automatisch im AlgoContext
|
||||||
|
gezählt werden. Swaps zählen je 2 reads + 2 writes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, ctx: AlgoContext = None):
|
||||||
|
self._ctx = ctx if ctx is not None else AlgoContext()
|
||||||
|
self._heap = [] # Liste von (Int priority, item)
|
||||||
|
|
||||||
|
def insert(self, item, priority):
|
||||||
|
"""Fügt item mit der gegebenen Priorität ein."""
|
||||||
|
self._heap.append((Int(priority, self._ctx), item))
|
||||||
|
self._ctx.writes += 1
|
||||||
|
self._sift_up(len(self._heap) - 1)
|
||||||
|
|
||||||
|
def pop(self):
|
||||||
|
"""Entfernt das Element mit der höchsten Priorität und gibt es zurück."""
|
||||||
|
if self.is_empty():
|
||||||
|
raise IndexError("Priority queue is empty")
|
||||||
|
self._swap(0, len(self._heap) - 1)
|
||||||
|
self._ctx.reads += 1
|
||||||
|
_, item = self._heap.pop()
|
||||||
|
if self._heap:
|
||||||
|
self._sift_down(0)
|
||||||
|
return item
|
||||||
|
|
||||||
|
def peek(self):
|
||||||
|
"""Gibt das Element mit der höchsten Priorität zurück (ohne Entfernung)."""
|
||||||
|
if self.is_empty():
|
||||||
|
raise IndexError("Priority queue is empty")
|
||||||
|
self._ctx.reads += 1
|
||||||
|
return self._heap[0][1]
|
||||||
|
|
||||||
|
def is_empty(self):
|
||||||
|
"""Gibt True zurück, wenn die Warteschlange leer ist."""
|
||||||
|
return len(self._heap) == 0
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._heap)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
pairs = sorted(self._heap, key=lambda t: t[0].value, reverse=True)
|
||||||
|
return "PriorityQueue([" + ", ".join(f"{p.value}:{v}" for p, v in pairs) + "])"
|
||||||
|
|
||||||
|
# ── interne Heap-Operationen ───────────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# Terminologie-Hinweis: In der Vorlesung heißt die Abwärts-
|
||||||
|
# Operation MAX-HEAPIFY – sie stellt die Heap-Eigenschaft für einen
|
||||||
|
# Teilbaum wieder her. _sift_down ist funktional identisch damit, folgt
|
||||||
|
# aber der in der übrigen Literatur gebräuchlicheren Richtungsbezeichnung
|
||||||
|
# (sift = „sieben/sinken lassen").
|
||||||
|
#
|
||||||
|
# _sift_up hat kein direktes Gegenstück in der Vorlesung, weil Heapsort
|
||||||
|
# nie ein Element einfügt: Dort wird nur nach unten geheapifiziert.
|
||||||
|
# Für eine vollständige Priority Queue mit insert() wird jedoch auch die
|
||||||
|
# Aufwärts-Richtung benötigt.
|
||||||
|
|
||||||
|
def _swap(self, i, j):
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.writes += 2
|
||||||
|
self._heap[i], self._heap[j] = self._heap[j], self._heap[i]
|
||||||
|
|
||||||
|
def _sift_up(self, i):
|
||||||
|
while i > 0:
|
||||||
|
parent = (i - 1) // 2
|
||||||
|
if self._heap[i][0] > self._heap[parent][0]: # Int: 2 reads + 1 cmp
|
||||||
|
self._swap(i, parent)
|
||||||
|
i = parent
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
def _sift_down(self, i):
|
||||||
|
n = len(self._heap)
|
||||||
|
while True:
|
||||||
|
largest, left, right = i, 2 * i + 1, 2 * i + 2
|
||||||
|
if left < n and self._heap[left][0] > self._heap[largest][0]:
|
||||||
|
largest = left
|
||||||
|
if right < n and self._heap[right][0] > self._heap[largest][0]:
|
||||||
|
largest = right
|
||||||
|
if largest == i:
|
||||||
|
break
|
||||||
|
self._swap(i, largest)
|
||||||
|
i = largest
|
||||||
|
|
||||||
|
|
||||||
|
# ── Demo & Auswertung ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import random
|
||||||
|
|
||||||
|
SIZES = list(range(10, 210, 10))
|
||||||
|
metrics = {m: [] for m in ("comparisons", "reads", "writes")}
|
||||||
|
|
||||||
|
for n in SIZES:
|
||||||
|
ctx = AlgoContext()
|
||||||
|
pq = PriorityQueue(ctx)
|
||||||
|
for p in random.sample(range(n * 10), n):
|
||||||
|
pq.insert(None, p)
|
||||||
|
while not pq.is_empty():
|
||||||
|
pq.pop()
|
||||||
|
for m in metrics:
|
||||||
|
metrics[m].append(getattr(ctx, m))
|
||||||
|
|
||||||
|
titles = {"comparisons": "Vergleiche", "reads": "Lesezugriffe", "writes": "Schreibzugriffe"}
|
||||||
|
fig, axes = plt.subplots(len(metrics), 1, figsize=(10, 9), sharex=True)
|
||||||
|
|
||||||
|
for ax, (metric, values) in zip(axes, metrics.items()):
|
||||||
|
ax.plot(SIZES, values, marker='o', markersize=3)
|
||||||
|
ax.set_ylabel(titles[metric])
|
||||||
|
|
||||||
|
axes[0].set_title("PriorityQueue – n inserts + n pops")
|
||||||
|
axes[-1].set_xlabel("n")
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.show()
|
||||||
|
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
|
||||||
|
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_array import Array
|
||||||
|
from vorlesung.L03_fortgeschrittenes_sortieren.quick_sorting import quick_sort, partition
|
||||||
|
|
||||||
|
|
||||||
|
def median3(z: Array, l: int, r: int) -> int:
|
||||||
|
"""
|
||||||
|
Gibt den Index des wertmäßig mittleren Elements aus z[l], z[m], z[r] zurück.
|
||||||
|
Vergleiche werden durch den AlgoContext der Array-Elemente gezählt.
|
||||||
|
"""
|
||||||
|
m = (l + r) // 2
|
||||||
|
a, b, c = z[l], z[m], z[r]
|
||||||
|
if a <= b:
|
||||||
|
if b <= c:
|
||||||
|
return m # a <= b <= c → b ist Median
|
||||||
|
elif a <= c:
|
||||||
|
return r # a <= c < b → c ist Median
|
||||||
|
else:
|
||||||
|
return l # c < a <= b → a ist Median
|
||||||
|
else:
|
||||||
|
if a <= c:
|
||||||
|
return l # b < a <= c → a ist Median
|
||||||
|
elif b <= c:
|
||||||
|
return r # b <= c < a → c ist Median
|
||||||
|
else:
|
||||||
|
return m # c < b < a → b ist Median
|
||||||
|
|
||||||
|
|
||||||
|
def partition_median3(z: Array, l: int, r: int, ctx: AlgoContext) -> int:
|
||||||
|
pivot_idx = median3(z, l, r)
|
||||||
|
if pivot_idx != r:
|
||||||
|
z.swap(pivot_idx, r) # Pivot an den rechten Rand
|
||||||
|
return partition(z, l, r, ctx)
|
||||||
|
|
||||||
|
|
||||||
|
def quick_sort_m3(z: Array, ctx: AlgoContext, l: int = None, r: int = None):
|
||||||
|
if l is None:
|
||||||
|
l = 0
|
||||||
|
if r is None:
|
||||||
|
r = len(z) - 1
|
||||||
|
if l < r:
|
||||||
|
q = partition_median3(z, l, r, ctx)
|
||||||
|
quick_sort_m3(z, ctx, l, q - 1)
|
||||||
|
quick_sort_m3(z, ctx, q + 1, r)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
SIZES = list(range(10, 210, 10))
|
||||||
|
|
||||||
|
algorithms = [
|
||||||
|
("Quick (klassisch)", quick_sort),
|
||||||
|
("Quick (Median-3)", quick_sort_m3),
|
||||||
|
]
|
||||||
|
|
||||||
|
comparisons = {name: [] for name, _ in algorithms}
|
||||||
|
writes = {name: [] for name, _ in algorithms}
|
||||||
|
|
||||||
|
for name, sort_func in algorithms:
|
||||||
|
ctx = AlgoContext()
|
||||||
|
for n in SIZES:
|
||||||
|
ctx.reset()
|
||||||
|
z = Array.random(n, -1000, 1000, ctx)
|
||||||
|
sort_func(z, ctx)
|
||||||
|
comparisons[name].append(ctx.comparisons)
|
||||||
|
writes[name].append(ctx.writes)
|
||||||
|
|
||||||
|
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
|
||||||
|
|
||||||
|
for name in comparisons:
|
||||||
|
ax1.plot(SIZES, comparisons[name], label=name, marker='o', markersize=3)
|
||||||
|
ax1.set_ylabel("Vergleiche")
|
||||||
|
ax1.set_title("Quick Sort: klassisch vs. Median-of-Three")
|
||||||
|
ax1.legend()
|
||||||
|
|
||||||
|
for name in writes:
|
||||||
|
ax2.plot(SIZES, writes[name], label=name, marker='o', markersize=3)
|
||||||
|
ax2.set_ylabel("Schreibzugriffe")
|
||||||
|
ax2.set_xlabel("n")
|
||||||
|
ax2.legend()
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.show()
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
|
||||||
|
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_array import Array
|
||||||
|
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
|
||||||
|
|
||||||
|
ctx = AlgoContext()
|
||||||
|
values = Array.from_file('data/seq0.txt', ctx)
|
||||||
|
tree = BinaryTree(ctx)
|
||||||
|
for cell in values:
|
||||||
|
tree.insert(cell.value)
|
||||||
|
|
||||||
|
# graph_traversal() erzeugt eine DOT-Datei und rendert sie als PDF.
|
||||||
|
# Die Datei landet im Verzeichnis, aus dem das Skript gestartet wird.
|
||||||
|
tree.graph_traversal()
|
||||||
|
|
||||||
|
# ── Antworten zu den Beobachtungsfragen ──────────────────────────────────────
|
||||||
|
|
||||||
|
# Welcher Knoten ist die Wurzel?
|
||||||
|
# Das erste eingefügte Element wird immer die Wurzel, da der Baum anfangs
|
||||||
|
# leer ist. Hier ist das -59 (erste Zeile von seq0.txt).
|
||||||
|
print("Wurzel:", tree.root.value) # -59
|
||||||
|
|
||||||
|
# Wie viele Blattknoten hat der Baum?
|
||||||
|
# Ein Blattknoten hat weder ein linkes noch ein rechtes Kind.
|
||||||
|
blattknoten = []
|
||||||
|
def merke_blatt(node):
|
||||||
|
if node.left is None and node.right is None:
|
||||||
|
blattknoten.append(node.value)
|
||||||
|
tree.in_order_traversal(merke_blatt)
|
||||||
|
print("Blattknoten:", blattknoten) # [-77, -50, 15, 48, 51, 58] → 6 Blätter
|
||||||
|
|
||||||
|
# Wie groß ist die Höhe des Baums?
|
||||||
|
# height() zählt rekursiv die längste Kante von der Wurzel zu einem Blatt.
|
||||||
|
# Mit 14 Elementen und zufälliger Reihenfolge ergibt sich hier Höhe 6.
|
||||||
|
print("Höhe:", tree.root.height()) # 6
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
|
||||||
|
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_array import Array
|
||||||
|
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
|
||||||
|
|
||||||
|
ctx = AlgoContext()
|
||||||
|
values = Array.from_file('data/seq0.txt', ctx)
|
||||||
|
tree = BinaryTree(ctx)
|
||||||
|
for cell in values:
|
||||||
|
tree.insert(cell.value)
|
||||||
|
|
||||||
|
# ── in_order_traversal ───────────────────────────────────────────────────────
|
||||||
|
print("In-Order:")
|
||||||
|
tree.in_order_traversal(lambda node: print(node.value, end=" "))
|
||||||
|
print()
|
||||||
|
# Ausgabe: -87 -77 -59 -50 14 15 34 46 47 48 50 51 52 58
|
||||||
|
#
|
||||||
|
# Warum sortiert?
|
||||||
|
# Die BST-Eigenschaft garantiert: linker Teilbaum < Wurzel < rechter Teilbaum.
|
||||||
|
# In-Order besucht genau in dieser Reihenfolge (links → Wurzel → rechts),
|
||||||
|
# was rekursiv angewendet eine aufsteigende Sortierung ergibt.
|
||||||
|
# In-Order-Traversal eines BST entspricht damit einer Sortierung der Schlüssel.
|
||||||
|
|
||||||
|
# ── level_order_traversal ────────────────────────────────────────────────────
|
||||||
|
print("\nLevel-Order:")
|
||||||
|
tree.level_order_traversal(lambda node, level: print(f"Ebene {level}: {node.value}"))
|
||||||
|
# Ausgabe: Wurzel zuerst, dann alle Knoten der Ebene 1, dann Ebene 2, …
|
||||||
|
#
|
||||||
|
# Was zeigt level_order, was in_order nicht zeigt?
|
||||||
|
# level_order_traversal entspricht einer Breitensuche (BFS). Sie zeigt,
|
||||||
|
# auf welcher Tiefenebene sich jeder Knoten befindet, und damit die
|
||||||
|
# tatsächliche Struktur des Baums – nicht nur die sortierte Schlüsselfolge.
|
||||||
|
# Zwei BSTs mit denselben Schlüsseln liefern identische in_order-Ausgaben,
|
||||||
|
# aber unterschiedliche level_order-Ausgaben, wenn ihre Struktur verschieden ist.
|
||||||
|
|
||||||
|
# ── graph_traversal ──────────────────────────────────────────────────────────
|
||||||
|
# Relevante Stelle in bin_tree.py:
|
||||||
|
#
|
||||||
|
# def graph_traversal(self):
|
||||||
|
# def define_node(node, level, line):
|
||||||
|
# node.graphviz_rep(level, line, dot) ← Knoten positionieren
|
||||||
|
# ...
|
||||||
|
# self.tree_structure_traversal(define_node) ← Traversierung
|
||||||
|
# _rec(self.root) ← Kanten eintragen
|
||||||
|
#
|
||||||
|
# graph_traversal nutzt intern tree_structure_traversal – eine In-Order-
|
||||||
|
# Traversal, die zusätzlich zu jedem Knoten seine Tiefe (level) und seine
|
||||||
|
# In-Order-Position (line) mitliefert.
|
||||||
|
#
|
||||||
|
# level → y-Koordinate im Graphviz-Layout: je tiefer im Baum, desto weiter
|
||||||
|
# unten in der Abbildung (negiert: pos=f"{col},{-row}!")
|
||||||
|
# line → x-Koordinate: die In-Order-Position ergibt automatisch eine
|
||||||
|
# kollisionsfreie horizontale Anordnung ohne überlappende Knoten.
|
||||||
|
#
|
||||||
|
# tree_structure_traversal existiert also nicht als eigenständige Ausgabe-
|
||||||
|
# Traversal, sondern als Hilfsmittel für die Positionsberechnung in graph_traversal.
|
||||||
|
# Die Traversierungsreihenfolge ist identisch mit in_order_traversal.
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
|
||||||
|
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
|
||||||
|
|
||||||
|
# ── Entarteter BST: sortierte Eingabe ────────────────────────────────────────
|
||||||
|
|
||||||
|
ctx = AlgoContext()
|
||||||
|
tree_sorted = BinaryTree(ctx)
|
||||||
|
for v in range(1, 9):
|
||||||
|
tree_sorted.insert(v)
|
||||||
|
|
||||||
|
tree_sorted.graph_traversal() # → entarteter Baum sichtbar: eine gerade Kette
|
||||||
|
|
||||||
|
print("Höhe (sortierte Eingabe):", tree_sorted.root.height()) # 8
|
||||||
|
|
||||||
|
ctx.comparisons = 0
|
||||||
|
tree_sorted.search(8)
|
||||||
|
print("Vergleiche search(8) im entarteten Baum:", ctx.comparisons) # 16
|
||||||
|
# Jeder Knoten (1 bis 8) erfordert zwei Vergleiche: value < node? (nein)
|
||||||
|
# und value > node? (ja, bis der gesuchte Knoten gefunden ist).
|
||||||
|
# Das entspricht einer linearen Suche: O(n).
|
||||||
|
|
||||||
|
# ── Referenz: ausgewogene Einfügereihenfolge ─────────────────────────────────
|
||||||
|
|
||||||
|
ctx2 = AlgoContext()
|
||||||
|
tree_balanced = BinaryTree(ctx2)
|
||||||
|
for v in [4, 2, 6, 1, 3, 5, 7, 8]: # Mitte zuerst → ausgewogener Baum
|
||||||
|
tree_balanced.insert(v)
|
||||||
|
|
||||||
|
print("\nHöhe (ausgewogene Eingabe):", tree_balanced.root.height()) # 4
|
||||||
|
|
||||||
|
ctx2.comparisons = 0
|
||||||
|
tree_balanced.search(8)
|
||||||
|
print("Vergleiche search(8) im ausgewogenen Baum:", ctx2.comparisons) # 8
|
||||||
|
|
||||||
|
# ── Antworten zu den Analysefragen ───────────────────────────────────────────
|
||||||
|
|
||||||
|
# Wie viele Vergleiche benötigt search(8) im entarteten Baum?
|
||||||
|
# 16 – der Baum entartet zur verketteten Liste, jeder Knoten wird besucht.
|
||||||
|
|
||||||
|
# Wie viele im ausgewogenen Baum mit 8 Knoten?
|
||||||
|
# 8 – die Höhe beträgt 4, jede Ebene kostet 2 Vergleiche (< und >).
|
||||||
|
|
||||||
|
# Welche Eingabereihenfolge erzeugt immer einen ausgewogenen BST?
|
||||||
|
# Median-First: stets den Median der aktuellen Teilfolge zuerst einfügen.
|
||||||
|
# Das setzt voraus, dass alle Elemente bereits bekannt sind – bei dynamischen
|
||||||
|
# Einfügungen (ein Element nach dem anderen, ohne Vorwissen) ist das nicht
|
||||||
|
# möglich. Deshalb reicht es nicht, nur die Einfügereihenfolge zu steuern.
|
||||||
|
|
||||||
|
# Was muss eine Datenstruktur garantieren?
|
||||||
|
# Sie muss die Baumhöhe nach jeder Einfüge- oder Löschoperation automatisch
|
||||||
|
# auf O(log n) begrenzen – unabhängig von der Eingabereihenfolge.
|
||||||
|
# AVL-Bäume erreichen das, indem sie nach jeder Operation den Balance-Faktor
|
||||||
|
# prüfen und bei Bedarf Rotationen durchführen.
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
|
||||||
|
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from vorlesung.L05_binaere_baeume.avl_tree import AVLTree
|
||||||
|
|
||||||
|
ctx = AlgoContext()
|
||||||
|
tree = AVLTree(ctx)
|
||||||
|
for v in [10, 5, 15, 3, 7, 12, 20, 1, 4, 6, 8]:
|
||||||
|
tree.insert(v)
|
||||||
|
|
||||||
|
print("Ausgangszustand:")
|
||||||
|
tree.in_order_traversal(lambda n: print(n.value, end=" "))
|
||||||
|
print()
|
||||||
|
tree.graph_traversal()
|
||||||
|
|
||||||
|
# ── Fall 1: Blattknoten löschen (1 ist Blatt) ────────────────────────────────
|
||||||
|
tree.delete(1)
|
||||||
|
print("\nNach Löschen von 1 (Blatt):")
|
||||||
|
tree.in_order_traversal(lambda n: print(n.value, end=" "))
|
||||||
|
print()
|
||||||
|
tree.graph_traversal()
|
||||||
|
|
||||||
|
# ── Fall 2: Knoten mit einem Kind löschen (3 hat jetzt nur noch Kind 4) ──────
|
||||||
|
tree.delete(3)
|
||||||
|
print("\nNach Löschen von 3 (ein Kind):")
|
||||||
|
tree.in_order_traversal(lambda n: print(n.value, end=" "))
|
||||||
|
print()
|
||||||
|
tree.graph_traversal()
|
||||||
|
|
||||||
|
# ── Fall 3: Knoten mit zwei Kindern löschen (10 hat linkes Kind 5, rechtes 15)
|
||||||
|
tree.delete(10)
|
||||||
|
print("\nNach Löschen von 10 (zwei Kinder):")
|
||||||
|
tree.in_order_traversal(lambda n: print(n.value, end=" "))
|
||||||
|
print()
|
||||||
|
tree.graph_traversal()
|
||||||
|
# Inorder-Nachfolger von 10 ist 12 (kleinstes Element im rechten Teilbaum).
|
||||||
|
# 12 übernimmt den Platz von 10, anschließend wird rebalanciert.
|
||||||
|
|
||||||
|
# ── Antworten zu den Verständnisfragen ───────────────────────────────────────
|
||||||
|
|
||||||
|
# Warum muss node.parent = parent nach dem Löschen neu gesetzt werden?
|
||||||
|
#
|
||||||
|
# BinaryTree kennt keine parent-Zeiger — delete() gibt lediglich
|
||||||
|
# (nachfolgender_knoten, elternknoten) als plain-Python-Tupel zurück.
|
||||||
|
# AVLTree pflegt parent-Zeiger, weil balance() sie benötigt, um den Pfad
|
||||||
|
# von einem Knoten aufwärts zur Wurzel zu traversieren.
|
||||||
|
# Ohne die Aktualisierung würde balance() an einem veralteten parent-Zeiger
|
||||||
|
# hängen und die Rebalancierung unvollständig oder fehlerhaft ausführen.
|
||||||
|
|
||||||
|
# Warum beginnt die Rebalancierung mit balance(parent) statt balance(node)?
|
||||||
|
#
|
||||||
|
# Das Löschen verändert die Höhe des Teilbaums, in dem der Knoten stand.
|
||||||
|
# Diese Höhenänderung kann den Balance-Faktor des Elternknotens (und aller
|
||||||
|
# Vorfahren bis zur Wurzel) aus dem Gleichgewicht bringen.
|
||||||
|
# Der gelöschte (oder ersetzte) Knoten selbst ist entweder weg oder ein Blatt
|
||||||
|
# — er ist kein sinnvoller Startpunkt für die Aufwärts-Traversal.
|
||||||
|
# balance(parent) startet genau dort, wo die Höhenänderung zuerst spürbar ist.
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
|
||||||
|
|
||||||
|
import random
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
|
||||||
|
from vorlesung.L05_binaere_baeume.avl_tree import AVLTree
|
||||||
|
|
||||||
|
# ── Theoretische Herleitung ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# AVL-Baum / ausgeglichener BST
|
||||||
|
# Bei jedem Schritt der Suche wird genau ein Teilbaum mit (ungefähr) halber
|
||||||
|
# Größe weiterverfolgt:
|
||||||
|
#
|
||||||
|
# T(n) = T(n/2) + O(1)
|
||||||
|
#
|
||||||
|
# Master-Theorem: a=1, b=2, f(n)=O(1)
|
||||||
|
# log_b(a) = log_2(1) = 0 → f(n) = O(n^0) = O(n^log_b(a))
|
||||||
|
# → Fall 2: T(n) = O(log n)
|
||||||
|
|
||||||
|
# BST – Worst Case (sortierte Eingabe, entarteter Baum)
|
||||||
|
# Der Baum ist eine verkettete Liste; jeder Schritt reduziert das Problem um 1:
|
||||||
|
#
|
||||||
|
# T(n) = T(n-1) + O(1)
|
||||||
|
#
|
||||||
|
# Kein Master-Theorem (b > 1 gefordert). Direkte Auflösung durch Entfalten:
|
||||||
|
# T(n) = T(n-2) + 2·c = … = T(0) + n·c → O(n)
|
||||||
|
|
||||||
|
# BST – zufällige Eingabe (Expected Case)
|
||||||
|
# Bei zufälliger Einfügereihenfolge teilt die Wurzel den Schlüsselraum im
|
||||||
|
# Erwartungswert in zwei ungefähr gleich große Hälften – analog zum
|
||||||
|
# durchschnittlichen Fall bei Quicksort. Daraus folgt eine erwartete Höhe
|
||||||
|
# von O(log n), empirisch ca. 2,5 · log₂(n). Ein formaler Beweis erfordert
|
||||||
|
# eine Wahrscheinlichkeitsanalyse über alle n! Permutationen.
|
||||||
|
|
||||||
|
# ── Empirische Messung ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
SIZES = [10, 100, 500]
|
||||||
|
|
||||||
|
print(f"{'n':>5} {'BST zufällig':>14} {'BST sortiert':>14} {'AVL sortiert':>14}")
|
||||||
|
print("-" * 55)
|
||||||
|
|
||||||
|
for n in SIZES:
|
||||||
|
# BST, zufällige Eingabe
|
||||||
|
ctx = AlgoContext()
|
||||||
|
tree = BinaryTree(ctx)
|
||||||
|
values = random.sample(range(n * 10), n)
|
||||||
|
for v in values:
|
||||||
|
tree.insert(v)
|
||||||
|
ctx.comparisons = 0
|
||||||
|
tree.search(values[-1])
|
||||||
|
cmp_random = ctx.comparisons
|
||||||
|
|
||||||
|
# BST, sortierte Eingabe
|
||||||
|
ctx = AlgoContext()
|
||||||
|
tree = BinaryTree(ctx)
|
||||||
|
for v in range(1, n + 1):
|
||||||
|
tree.insert(v)
|
||||||
|
ctx.comparisons = 0
|
||||||
|
tree.search(n)
|
||||||
|
cmp_sorted = ctx.comparisons
|
||||||
|
|
||||||
|
# AVL-Baum, sortierte Eingabe
|
||||||
|
ctx = AlgoContext()
|
||||||
|
tree = AVLTree(ctx)
|
||||||
|
for v in range(1, n + 1):
|
||||||
|
tree.insert(v)
|
||||||
|
ctx.comparisons = 0
|
||||||
|
tree.search(n)
|
||||||
|
cmp_avl = ctx.comparisons
|
||||||
|
|
||||||
|
print(f"{n:>5} {cmp_random:>14} {cmp_sorted:>14} {cmp_avl:>14}")
|
||||||
|
|
||||||
|
# ── Abschlussfrage ────────────────────────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# Stimmen die Messwerte mit den theoretischen Komplexitätsklassen überein?
|
||||||
|
#
|
||||||
|
# BST sortiert wächst linear mit n (2·n Vergleiche für das letzte Element),
|
||||||
|
# was die hergeleitete O(n)-Komplexität bestätigt.
|
||||||
|
#
|
||||||
|
# BST zufällig und AVL sortiert wachsen logarithmisch: für n=500 liegen die
|
||||||
|
# Vergleiche bei ca. 20 bzw. 18 – weit unter dem linearen Worst Case.
|
||||||
|
# Das bestätigt O(log n) für beide, wobei der AVL-Baum die Schranke
|
||||||
|
# garantiert und der zufällige BST sie nur im Erwartungswert einhält.
|
||||||
@@ -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())
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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)
|
||||||
@@ -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."
|
||||||
|
)
|
||||||
@@ -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}")
|
||||||
@@ -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."
|
||||||
|
)
|
||||||
@@ -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)."
|
||||||
|
)
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
from vorlesung.L08_graphen.graph import AdjacencyListGraph
|
||||||
|
|
||||||
|
GRID = [
|
||||||
|
"Sabqponm",
|
||||||
|
"abcryxxl",
|
||||||
|
"accszExk",
|
||||||
|
"acctuvwj",
|
||||||
|
"abdefghi",
|
||||||
|
]
|
||||||
|
|
||||||
|
def height(ch):
|
||||||
|
if ch == 'S': return ord('a')
|
||||||
|
if ch == 'E': return ord('z')
|
||||||
|
return ord(ch)
|
||||||
|
|
||||||
|
def build_graph(grid):
|
||||||
|
rows = len(grid)
|
||||||
|
cols = len(grid[0])
|
||||||
|
g = AdjacencyListGraph()
|
||||||
|
for r in range(rows):
|
||||||
|
for c in range(cols):
|
||||||
|
g.insert_vertex(f"{r}_{c}")
|
||||||
|
for r in range(rows):
|
||||||
|
for c in range(cols):
|
||||||
|
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||||
|
nr, nc = r + dr, c + dc
|
||||||
|
if 0 <= nr < rows and 0 <= nc < cols:
|
||||||
|
if height(grid[nr][nc]) - height(grid[r][c]) <= 1:
|
||||||
|
g.connect(f"{r}_{c}", f"{nr}_{nc}")
|
||||||
|
return g, rows, cols
|
||||||
|
|
||||||
|
def find_start_end(grid):
|
||||||
|
start = end = None
|
||||||
|
for r, row in enumerate(grid):
|
||||||
|
for c, ch in enumerate(row):
|
||||||
|
if ch == 'S': start = f"{r}_{c}"
|
||||||
|
if ch == 'E': end = f"{r}_{c}"
|
||||||
|
return start, end
|
||||||
|
|
||||||
|
def solve(grid, label="Beispielgelände"):
|
||||||
|
g, rows, cols = build_graph(grid)
|
||||||
|
start, end = find_start_end(grid)
|
||||||
|
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
dist_map, pred_map = g.bfs(start)
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
|
||||||
|
pfad = g.path(end, pred_map)
|
||||||
|
|
||||||
|
def fmt(node):
|
||||||
|
r, c = node.split("_")
|
||||||
|
return f"({r},{c})"
|
||||||
|
|
||||||
|
print(f"=== {label} ===")
|
||||||
|
print(f"|V| = {rows * cols}, |E| <= {rows * cols * 4} (real |E| gezählt separat)")
|
||||||
|
print(f"Pfadlänge: {len(pfad) - 1} Schritte")
|
||||||
|
print(f"Pfad: {' -> '.join(fmt(n) for n in pfad)}")
|
||||||
|
print(f"BFS-Laufzeit: {(t1 - t0) * 1000:.4f} ms")
|
||||||
|
print()
|
||||||
|
return rows * cols, t1 - t0
|
||||||
|
|
||||||
|
# --- a) Modellierung ---------------------------------------------------------
|
||||||
|
# Adjazenzliste ist besser geeignet: Das Grid ist licht (sparse).
|
||||||
|
# Jeder Knoten hat maximal 4 Nachbarn => |E| <= 4|V|.
|
||||||
|
# Eine Adjazenzmatrix hätte |V|^2 Einträge; für ein 41x122-Grid wären das
|
||||||
|
# über 25 Millionen Einträge, obwohl nur ~4*|V| davon belegt wären.
|
||||||
|
|
||||||
|
# --- b) Pfadsuche -------------------------------------------------------------
|
||||||
|
v_small, t_small = solve(GRID, "Beispielgelände (8x5)")
|
||||||
|
|
||||||
|
# --- c) AoC-Karte (optional) --------------------------------------------------
|
||||||
|
if len(sys.argv) > 1 and os.path.exists(sys.argv[1]):
|
||||||
|
with open(sys.argv[1]) as f:
|
||||||
|
aoc_grid = [line.rstrip() for line in f if line.strip()]
|
||||||
|
v_large, t_large = solve(aoc_grid, f"AoC-Karte ({len(aoc_grid)}x{len(aoc_grid[0])})")
|
||||||
|
ratio_v = v_large / v_small
|
||||||
|
ratio_t = t_large / t_small if t_small > 0 else float('inf')
|
||||||
|
print(f"Verhältnis |V|: {ratio_v:.1f}x")
|
||||||
|
print(f"Verhältnis Laufzeit: {ratio_t:.1f}x")
|
||||||
|
print(f"Lineares Wachstum bestätigt: {'ja' if abs(ratio_t / ratio_v - 1) < 0.5 else 'nein'}")
|
||||||
|
|
||||||
|
# --- d) Optional: bester Startpunkt (AoC Teil 2) ------------------------------
|
||||||
|
# Hinweis: Statt BFS von jedem 'a'-Feld zu starten, dreht man den Graphen um
|
||||||
|
# und startet eine einzige BFS von E rückwärts. Der erste erreichte 'a'-Knoten
|
||||||
|
# liefert den kürzesten Pfad.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
from vorlesung.L08_graphen.graph import AdjacencyListGraph
|
||||||
|
|
||||||
|
DEPENDENCIES = {
|
||||||
|
"main": ["parser", "codegen"],
|
||||||
|
"parser": ["lexer", "ast"],
|
||||||
|
"codegen": ["ast", "optimizer"],
|
||||||
|
"optimizer": ["utils"],
|
||||||
|
"ast": ["utils"],
|
||||||
|
"lexer": [],
|
||||||
|
"utils": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- a) Graph aufbauen -------------------------------------------------------
|
||||||
|
# Kantendefinition: A -> B bedeutet "A muss vor B gebaut werden" (B hängt von A ab).
|
||||||
|
# Kanten zeigen also von der Voraussetzung zum Abhängigen – nur so liefert
|
||||||
|
# die absteigende Sortierung nach leave_map die korrekte Baureihenfolge.
|
||||||
|
g = AdjacencyListGraph()
|
||||||
|
for module in DEPENDENCIES:
|
||||||
|
g.insert_vertex(module)
|
||||||
|
for module, deps in DEPENDENCIES.items():
|
||||||
|
for dep in deps:
|
||||||
|
g.connect(dep, module) # dep -> module: dep muss vor module gebaut werden
|
||||||
|
|
||||||
|
g.graph("BuildDependencies")
|
||||||
|
|
||||||
|
# --- b) DFS ausführen --------------------------------------------------------
|
||||||
|
enter_map, leave_map, pred_map = g.dfs()
|
||||||
|
|
||||||
|
print("Zeitmarken (DFS):")
|
||||||
|
for v in sorted(enter_map, key=lambda v: enter_map[v]):
|
||||||
|
print(f" {v.value:12s} begin={enter_map[v]:2d} end={leave_map[v]:2d}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# --- c) Topologische Sortierung ----------------------------------------------
|
||||||
|
sorted_vertices = sorted(leave_map, key=lambda v: leave_map[v], reverse=True)
|
||||||
|
reihenfolge = [v.value for v in sorted_vertices]
|
||||||
|
print("Topologische Reihenfolge (Build-Reihenfolge):")
|
||||||
|
print(" " + " -> ".join(reihenfolge))
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Überprüfung: Für jede Abhängigkeit A->B muss A nach B in der Liste kommen.
|
||||||
|
pos = {name: i for i, name in enumerate(reihenfolge)}
|
||||||
|
ok = all(pos[dep] < pos[module]
|
||||||
|
for module, deps in DEPENDENCIES.items()
|
||||||
|
for dep in deps)
|
||||||
|
print(f"Alle Abhängigkeiten eingehalten: {ok}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# --- d) Komplexität ----------------------------------------------------------
|
||||||
|
v_count = len(list(g.all_vertices()))
|
||||||
|
e_count = sum(len(g.get_adjacent_vertices(v.value)) for v in g.all_vertices())
|
||||||
|
print(f"|V| = {v_count}, |E| = {e_count}")
|
||||||
|
# Jeder Knoten wird genau einmal grau (begin) und einmal schwarz (end) => O(|V|)
|
||||||
|
# Jede Kante wird beim ersten Besuch des Ausgangsknotens genau einmal betrachtet => O(|E|)
|
||||||
|
# Zusammen: O(|V| + |E|)
|
||||||
@@ -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")
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_int import Int
|
||||||
|
from utils.algo_array import Array
|
||||||
|
from utils.algo_range import Range
|
||||||
|
from utils.algo_path import path
|
||||||
|
from utils.algo_priority_queue import PriorityQueue
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from random import randint
|
||||||
|
from utils.algo_context import AlgoContext, _NullContext, NULL_CTX
|
||||||
|
from utils.algo_int import Int
|
||||||
|
from utils.algo_path import path
|
||||||
|
|
||||||
|
|
||||||
|
class Array:
|
||||||
|
"""
|
||||||
|
Instrumentiertes Array.
|
||||||
|
|
||||||
|
Jede Zelle ist ein Int-Objekt, das Operationen an den gemeinsamen
|
||||||
|
AlgoContext meldet.
|
||||||
|
|
||||||
|
Zugriff
|
||||||
|
-------
|
||||||
|
arr[i] gibt die Int-Zelle zurück (kein Zähler)
|
||||||
|
arr[i] = v setzt den Wert der Zelle (1 write, +1 read falls v ein Int)
|
||||||
|
len(arr) gibt plain int zurück
|
||||||
|
arr.length() gibt Int zurück (für Algorithmen nützlich)
|
||||||
|
arr.swap(i,j) tauscht zwei Elemente (2 reads + 2 writes)
|
||||||
|
|
||||||
|
Hinweis: arr[i] gibt eine Referenz auf die Zelle zurück.
|
||||||
|
Lesende Verwendung (Vergleich, Arithmetik) zählt dort – nicht beim Zugriff selbst.
|
||||||
|
|
||||||
|
Fabrikmethoden
|
||||||
|
--------------
|
||||||
|
Array.random(n, min_val, max_val, ctx) zufällige Werte
|
||||||
|
Array.sorted(n, ctx) aufsteigend sortiert 0..n-1
|
||||||
|
Array.from_file(filename, ctx) Werte aus Textdatei (eine Zahl pro Zeile)
|
||||||
|
|
||||||
|
Beispiel
|
||||||
|
--------
|
||||||
|
ctx = AlgoContext()
|
||||||
|
z = Array.random(10, 0, 99, ctx)
|
||||||
|
if z[0] > z[1]: # 2 reads, 1 comparison
|
||||||
|
z.swap(0, 1) # 2 reads, 2 writes
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, data: list, ctx: AlgoContext | _NullContext):
|
||||||
|
self._ctx = ctx
|
||||||
|
self._cells = [Int(v, ctx) for v in data]
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Element-Zugriff
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def __getitem__(self, index) -> Int:
|
||||||
|
"""Gibt die Int-Zelle zurück. Kein Zähler – Zählung erfolgt bei Nutzung."""
|
||||||
|
return self._cells[int(index)]
|
||||||
|
|
||||||
|
def __setitem__(self, index, value):
|
||||||
|
"""
|
||||||
|
Setzt den Wert der Zelle.
|
||||||
|
|
||||||
|
Delegiert an Int.set() → 1 write (+1 read falls value ein Int ist).
|
||||||
|
"""
|
||||||
|
self._cells[int(index)].set(value)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Länge
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self._cells)
|
||||||
|
|
||||||
|
def length(self) -> Int:
|
||||||
|
"""Gibt die Länge als Int zurück (für Algorithmen, die mit Int rechnen)."""
|
||||||
|
return Int(len(self._cells), self._ctx)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Iteration
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._cells)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Tausch
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def swap(self, i, j):
|
||||||
|
"""
|
||||||
|
Tauscht die Werte an Position i und j.
|
||||||
|
|
||||||
|
Zählt: 2 reads + 2 writes.
|
||||||
|
"""
|
||||||
|
ci = self._cells[int(i)]
|
||||||
|
cj = self._cells[int(j)]
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.writes += 2
|
||||||
|
ci._value, cj._value = cj._value, ci._value
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Darstellung
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '[' + ', '.join(str(c) for c in self._cells) + ']'
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Array({[c.value for c in self._cells]})"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Fabrikmethoden
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def random(n: int, min_val: int, max_val: int, ctx: AlgoContext) -> Array:
|
||||||
|
"""Erzeugt ein Array mit n zufälligen Werten aus [min_val, max_val]."""
|
||||||
|
n = int(n)
|
||||||
|
return Array([randint(min_val, max_val) for _ in range(n)], ctx)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sorted(n: int, ctx: AlgoContext) -> Array:
|
||||||
|
"""Erzeugt ein aufsteigend sortiertes Array 0, 1, …, n-1."""
|
||||||
|
n = int(n)
|
||||||
|
return Array(list(range(n)), ctx)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_file(filename: str, ctx: AlgoContext, limit: int | None = None) -> Array:
|
||||||
|
"""
|
||||||
|
Liest Ganzzahlen aus einer Textdatei (eine Zahl pro Zeile).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
filename : str
|
||||||
|
Pfad relativ zum Projektverzeichnis oder absolut.
|
||||||
|
ctx : AlgoContext
|
||||||
|
limit : int | None
|
||||||
|
Optionale Obergrenze für die Anzahl eingelesener Zeilen.
|
||||||
|
"""
|
||||||
|
filepath = path(filename)
|
||||||
|
with open(filepath) as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
if limit is not None:
|
||||||
|
lines = lines[:limit]
|
||||||
|
data = [int(line.strip()) for line in lines if line.strip()]
|
||||||
|
return Array(data, ctx)
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
class AlgoContext:
|
||||||
|
"""
|
||||||
|
Kontext für die Instrumentierung von Algorithmen.
|
||||||
|
|
||||||
|
Jeder Algorithmus erhält eine eigene Instanz – kein globaler Zustand.
|
||||||
|
Die Zähler werden von Int- und Array-Operationen automatisch erhöht.
|
||||||
|
|
||||||
|
Zähler
|
||||||
|
------
|
||||||
|
reads Lesezugriffe (bei Vergleichen und Arithmetik je Operand)
|
||||||
|
writes Schreibzugriffe (set, Zuweisung, augmented assignment)
|
||||||
|
comparisons Vergleiche (<, <=, >, >=, ==, !=)
|
||||||
|
additions Additionen (+, +=)
|
||||||
|
subtractions Subtraktionen (-, -=)
|
||||||
|
multiplications Multiplikationen (*, *=)
|
||||||
|
divisions Divisionen (/, //, %, /=, //=)
|
||||||
|
bitops Bitoperationen (&, |, ^, <<, >>)
|
||||||
|
|
||||||
|
Beispiel
|
||||||
|
--------
|
||||||
|
ctx = AlgoContext()
|
||||||
|
z = Array.random(20, -100, 100, ctx)
|
||||||
|
bubble_sort(z, ctx)
|
||||||
|
print(ctx)
|
||||||
|
ctx.save_stats(20)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.reads = 0
|
||||||
|
self.writes = 0
|
||||||
|
self.comparisons = 0
|
||||||
|
self.additions = 0
|
||||||
|
self.subtractions = 0
|
||||||
|
self.multiplications = 0
|
||||||
|
self.divisions = 0
|
||||||
|
self.bitops = 0
|
||||||
|
self._stats: dict[int, dict] = {}
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Setzt alle Zähler auf 0 zurück."""
|
||||||
|
self.reads = 0
|
||||||
|
self.writes = 0
|
||||||
|
self.comparisons = 0
|
||||||
|
self.additions = 0
|
||||||
|
self.subtractions = 0
|
||||||
|
self.multiplications = 0
|
||||||
|
self.divisions = 0
|
||||||
|
self.bitops = 0
|
||||||
|
|
||||||
|
def _snapshot(self) -> dict:
|
||||||
|
return {
|
||||||
|
"reads": self.reads,
|
||||||
|
"writes": self.writes,
|
||||||
|
"comparisons": self.comparisons,
|
||||||
|
"additions": self.additions,
|
||||||
|
"subtractions": self.subtractions,
|
||||||
|
"multiplications": self.multiplications,
|
||||||
|
"divisions": self.divisions,
|
||||||
|
"bitops": self.bitops,
|
||||||
|
}
|
||||||
|
|
||||||
|
def save_stats(self, n: int):
|
||||||
|
"""Speichert einen Schnappschuss der aktuellen Zähler für Eingabegröße n."""
|
||||||
|
self._stats[n] = self._snapshot()
|
||||||
|
|
||||||
|
def plot_stats(self, labels: list[str]):
|
||||||
|
"""
|
||||||
|
Zeichnet die gespeicherten Statistiken als Liniendiagramm.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
labels : list[str]
|
||||||
|
Zähler-Namen, z.B. ["comparisons", "writes"]
|
||||||
|
"""
|
||||||
|
data = self._stats
|
||||||
|
x = list(data.keys())
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(len(labels), 1, figsize=(8, 4 * len(labels)), sharex=True)
|
||||||
|
if len(labels) == 1:
|
||||||
|
axes = [axes]
|
||||||
|
|
||||||
|
for ax, label in zip(axes, labels):
|
||||||
|
y = [data[k][label] for k in x]
|
||||||
|
ax.plot(x, y, label=label)
|
||||||
|
ax.set_ylabel(label)
|
||||||
|
ax.legend()
|
||||||
|
|
||||||
|
plt.xlabel("n")
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
def summary(self) -> str:
|
||||||
|
"""Gibt alle Zähler als formatierten Text zurück."""
|
||||||
|
return (
|
||||||
|
f"Reads: {self.reads}\n"
|
||||||
|
f"Writes: {self.writes}\n"
|
||||||
|
f"Comparisons: {self.comparisons}\n"
|
||||||
|
f"Additions: {self.additions}\n"
|
||||||
|
f"Subtractions: {self.subtractions}\n"
|
||||||
|
f"Multiplications: {self.multiplications}\n"
|
||||||
|
f"Divisions: {self.divisions}\n"
|
||||||
|
f"Bitwise ops: {self.bitops}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.summary()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return (f"AlgoContext(reads={self.reads}, writes={self.writes}, "
|
||||||
|
f"comparisons={self.comparisons})")
|
||||||
|
|
||||||
|
|
||||||
|
class _NullContext:
|
||||||
|
"""
|
||||||
|
Kontext der alle Operationen stillschweigend ignoriert.
|
||||||
|
|
||||||
|
Wird intern von Range() verwendet, damit Schleifenindex-Arithmetik
|
||||||
|
standardmäßig nicht mitgezählt wird.
|
||||||
|
|
||||||
|
__setattr__ ist absichtlich ein no-op: ``ctx.reads += 1`` bleibt wirkungslos.
|
||||||
|
"""
|
||||||
|
reads = writes = comparisons = 0
|
||||||
|
additions = subtractions = multiplications = divisions = bitops = 0
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
pass # alle Schreibzugriffe ignorieren
|
||||||
|
|
||||||
|
def save_stats(self, n): pass
|
||||||
|
def reset(self): pass
|
||||||
|
|
||||||
|
|
||||||
|
NULL_CTX = _NullContext()
|
||||||
File renamed without changes.
@@ -0,0 +1,327 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from utils.algo_context import AlgoContext, _NullContext, NULL_CTX
|
||||||
|
|
||||||
|
|
||||||
|
class Int:
|
||||||
|
"""
|
||||||
|
Instrumentierter Integer-Typ.
|
||||||
|
|
||||||
|
Alle Operationen werden im zugehörigen AlgoContext gezählt.
|
||||||
|
Der Rohwert ist über das Attribut ``value`` direkt lesbar (kein Zähler),
|
||||||
|
was für Visualisierungen benötigt wird.
|
||||||
|
|
||||||
|
Zählregeln
|
||||||
|
----------
|
||||||
|
Vergleich (a < b): 2 reads + 1 comparison
|
||||||
|
Arithmetik (a + b): 2 reads + 1 arithmetische Operation → neues Int
|
||||||
|
Augmented (a += b): 2 reads + 1 arithmetische Operation + 1 write
|
||||||
|
set(v): 1 write (+1 read falls v ein Int ist)
|
||||||
|
|
||||||
|
Auto-Wrapping
|
||||||
|
-------------
|
||||||
|
Alle Operatoren akzeptieren auch plain-Python-Werte (int, float).
|
||||||
|
Diese werden intern zu Int(v, NULL_CTX) gewrappt, ohne Zähler zu erhöhen.
|
||||||
|
|
||||||
|
Beispiel
|
||||||
|
--------
|
||||||
|
ctx = AlgoContext()
|
||||||
|
a = Int(5, ctx)
|
||||||
|
b = Int(3, ctx)
|
||||||
|
if a > b: # 2 reads, 1 comparison
|
||||||
|
a += b # 2 reads, 1 addition, 1 write
|
||||||
|
print(a.value) # 8 (kein Zähler)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, value, ctx: AlgoContext | _NullContext = NULL_CTX):
|
||||||
|
if isinstance(value, Int):
|
||||||
|
self._value = value._value
|
||||||
|
else:
|
||||||
|
self._value = value
|
||||||
|
self._ctx = ctx
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Rohwert-Zugriff (für Visualisierung, kein Zähler)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
"""Rohwert für Visualisierung – wird nicht gezählt."""
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Schreiben
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def set(self, new_value):
|
||||||
|
"""
|
||||||
|
Setzt den Wert.
|
||||||
|
|
||||||
|
Zählt: 1 write (+1 read falls new_value ein Int ist)
|
||||||
|
"""
|
||||||
|
self._ctx.writes += 1
|
||||||
|
if isinstance(new_value, Int):
|
||||||
|
self._ctx.reads += 1
|
||||||
|
self._value = new_value._value
|
||||||
|
else:
|
||||||
|
self._value = new_value
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Interner Hilfshelfer
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _wrap(self, other) -> Int:
|
||||||
|
"""Wraps plain Python-Wert zu Int mit NULL_CTX (kein Zähler)."""
|
||||||
|
if isinstance(other, Int):
|
||||||
|
return other
|
||||||
|
return Int(other, NULL_CTX)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Vergleiche (2 reads + 1 comparison je Operation)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if other is None:
|
||||||
|
return False
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.comparisons += 1
|
||||||
|
return self._value == other._value
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
if other is None:
|
||||||
|
return True
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.comparisons += 1
|
||||||
|
return self._value != other._value
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.comparisons += 1
|
||||||
|
return self._value < other._value
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.comparisons += 1
|
||||||
|
return self._value <= other._value
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.comparisons += 1
|
||||||
|
return self._value > other._value
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.comparisons += 1
|
||||||
|
return self._value >= other._value
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Arithmetik (2 reads + 1 op → neues Int mit gleichem ctx)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.additions += 1
|
||||||
|
return Int(self._value + other._value, self._ctx)
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
return self._wrap(other).__add__(self)
|
||||||
|
|
||||||
|
def __sub__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.subtractions += 1
|
||||||
|
return Int(self._value - other._value, self._ctx)
|
||||||
|
|
||||||
|
def __rsub__(self, other):
|
||||||
|
return self._wrap(other).__sub__(self)
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.multiplications += 1
|
||||||
|
return Int(self._value * other._value, self._ctx)
|
||||||
|
|
||||||
|
def __rmul__(self, other):
|
||||||
|
return self._wrap(other).__mul__(self)
|
||||||
|
|
||||||
|
def __truediv__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.divisions += 1
|
||||||
|
return Int(self._value / other._value, self._ctx)
|
||||||
|
|
||||||
|
def __floordiv__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.divisions += 1
|
||||||
|
return Int(self._value // other._value, self._ctx)
|
||||||
|
|
||||||
|
def __mod__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.divisions += 1
|
||||||
|
return Int(self._value % other._value, self._ctx)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Augmented assignment (2 reads + 1 op + 1 write, in-place)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def __iadd__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.additions += 1
|
||||||
|
self._ctx.writes += 1
|
||||||
|
self._value += other._value
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __isub__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.subtractions += 1
|
||||||
|
self._ctx.writes += 1
|
||||||
|
self._value -= other._value
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __imul__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.multiplications += 1
|
||||||
|
self._ctx.writes += 1
|
||||||
|
self._value *= other._value
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __itruediv__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.divisions += 1
|
||||||
|
self._ctx.writes += 1
|
||||||
|
self._value /= other._value
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __ifloordiv__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.divisions += 1
|
||||||
|
self._ctx.writes += 1
|
||||||
|
self._value //= other._value
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __imod__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.divisions += 1
|
||||||
|
self._ctx.writes += 1
|
||||||
|
self._value %= other._value
|
||||||
|
return self
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Bitoperationen (2 reads + 1 bitop + 1 write für in-place)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def __and__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.bitops += 1
|
||||||
|
return Int(self._value & other._value, self._ctx)
|
||||||
|
|
||||||
|
def __or__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.bitops += 1
|
||||||
|
return Int(self._value | other._value, self._ctx)
|
||||||
|
|
||||||
|
def __xor__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.bitops += 1
|
||||||
|
return Int(self._value ^ other._value, self._ctx)
|
||||||
|
|
||||||
|
def __lshift__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.bitops += 1
|
||||||
|
return Int(self._value << other._value, self._ctx)
|
||||||
|
|
||||||
|
def __rshift__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.bitops += 1
|
||||||
|
return Int(self._value >> other._value, self._ctx)
|
||||||
|
|
||||||
|
def __iand__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.bitops += 1
|
||||||
|
self._ctx.writes += 1
|
||||||
|
self._value &= other._value
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __ior__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.bitops += 1
|
||||||
|
self._ctx.writes += 1
|
||||||
|
self._value |= other._value
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __ixor__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.bitops += 1
|
||||||
|
self._ctx.writes += 1
|
||||||
|
self._value ^= other._value
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __ilshift__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.bitops += 1
|
||||||
|
self._ctx.writes += 1
|
||||||
|
self._value <<= other._value
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __irshift__(self, other):
|
||||||
|
other = self._wrap(other)
|
||||||
|
self._ctx.reads += 2
|
||||||
|
self._ctx.bitops += 1
|
||||||
|
self._ctx.writes += 1
|
||||||
|
self._value >>= other._value
|
||||||
|
return self
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Typkonvertierung und Darstellung
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return int(self._value)
|
||||||
|
|
||||||
|
def __float__(self):
|
||||||
|
return float(self._value)
|
||||||
|
|
||||||
|
def __index__(self):
|
||||||
|
"""Ermöglicht Verwendung als Listen-Index (z.B. arr[i])."""
|
||||||
|
return int(self._value)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self._value)
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return bool(self._value)
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
return Int(-self._value, self._ctx)
|
||||||
|
|
||||||
|
def __abs__(self):
|
||||||
|
return Int(abs(self._value), self._ctx)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self._value)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Int({self._value})"
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def path(filename) -> Path:
|
||||||
|
"""Gibt den absoluten Pfad zu einer Datei im Projektverzeichnis zurück.
|
||||||
|
|
||||||
|
Funktioniert unabhängig vom Arbeitsverzeichnis und der verwendeten IDE,
|
||||||
|
da der Pfad relativ zur Position dieses Moduls berechnet wird.
|
||||||
|
|
||||||
|
Beispiel
|
||||||
|
--------
|
||||||
|
from utils.algo_path import path
|
||||||
|
z = Array.from_file(path("data/seq0.txt"), ctx)
|
||||||
|
"""
|
||||||
|
project_dir = Path(__file__).resolve().parent.parent
|
||||||
|
return project_dir / filename
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
filename = path("data/seq0.txt")
|
||||||
|
print(filename)
|
||||||
|
print(filename.resolve())
|
||||||
|
print(filename.is_file())
|
||||||
|
print(filename.exists())
|
||||||
@@ -35,6 +35,6 @@ if __name__ == "__main__":
|
|||||||
pq.add_or_update('task3', float('inf'))
|
pq.add_or_update('task3', float('inf'))
|
||||||
|
|
||||||
print(pq.pop()) # Should print ('task1', 1)
|
print(pq.pop()) # Should print ('task1', 1)
|
||||||
pq.add_or_update('task2', 0) # Update priority of 'task1'
|
pq.add_or_update('task2', 0) # Update priority of 'task2'
|
||||||
print(pq.pop()) # Should print ('task2', 0)
|
print(pq.pop()) # Should print ('task2', 0)
|
||||||
print(pq.pop()) # Should print ('task3', 3)
|
print(pq.pop()) # Should print ('task3', 3)
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from utils.algo_context import AlgoContext, NULL_CTX
|
||||||
|
from utils.algo_int import Int
|
||||||
|
|
||||||
|
|
||||||
|
def Range(start_or_stop, stop=None, step: int = 1, ctx: AlgoContext = None):
|
||||||
|
"""
|
||||||
|
Drop-in Ersatz für range(), der Int-Objekte zurückgibt.
|
||||||
|
|
||||||
|
Wird wie Pythons range() aufgerufen:
|
||||||
|
Range(stop)
|
||||||
|
Range(start, stop)
|
||||||
|
Range(start, stop, step)
|
||||||
|
|
||||||
|
Indexarithmetik und Zählung
|
||||||
|
---------------------------
|
||||||
|
Ohne ctx-Argument (Standard) erhalten die erzeugten Indices einen NULL_CTX –
|
||||||
|
Arithmetik auf Loop-Indices (z.B. ``j - 1``) wird dann **nicht** gezählt.
|
||||||
|
Das entspricht dem üblichen Lehrbuchwunsch: nur Operationen auf Array-Inhalten
|
||||||
|
sollen in die Komplexitätsanalyse einfließen.
|
||||||
|
|
||||||
|
Mit ctx-Argument werden auch Indexoperationen gezählt:
|
||||||
|
for j in Range(n, ctx=ctx): ...
|
||||||
|
|
||||||
|
Beispiel
|
||||||
|
--------
|
||||||
|
ctx = AlgoContext()
|
||||||
|
z = Array.random(10, 0, 99, ctx)
|
||||||
|
for i in Range(len(z) - 1): # i ist Int, Arithmetik nicht gezählt
|
||||||
|
if z[i] > z[i + 1]: # Vergleich gezählt (z-Zellen haben ctx)
|
||||||
|
z.swap(i, i + 1)
|
||||||
|
"""
|
||||||
|
_ctx = ctx if ctx is not None else NULL_CTX
|
||||||
|
|
||||||
|
if stop is None:
|
||||||
|
start, stop_ = 0, int(start_or_stop)
|
||||||
|
else:
|
||||||
|
start, stop_ = int(start_or_stop), int(stop)
|
||||||
|
step = int(step)
|
||||||
|
|
||||||
|
assert step != 0, "Range: step darf nicht 0 sein"
|
||||||
|
|
||||||
|
num = start
|
||||||
|
if step > 0:
|
||||||
|
while num < stop_:
|
||||||
|
yield Int(num, _ctx)
|
||||||
|
num += step
|
||||||
|
else:
|
||||||
|
while num > stop_:
|
||||||
|
yield Int(num, _ctx)
|
||||||
|
num += step
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
MAX_VALUE = Literal(99999999999999999999)
|
|
||||||
MIN_VALUE = Literal(-99999999999999999999)
|
|
||||||
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
|
|
||||||
class Literal:
|
|
||||||
def __init__(self, value):
|
|
||||||
"""Initialisiert Literal."""
|
|
||||||
if isinstance(value, Literal):
|
|
||||||
self.value = value.value
|
|
||||||
else:
|
|
||||||
self.value = value
|
|
||||||
self.read_count = 0
|
|
||||||
self.compare_count = 0
|
|
||||||
|
|
||||||
def reset_counters(self):
|
|
||||||
"""Setzt alle Zähler auf 0 zurück."""
|
|
||||||
self.read_count = 0
|
|
||||||
self.compare_count = 0
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
"""Liest den Wert aus."""
|
|
||||||
self.read_count += 1
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
"""Vergleicht den Wert mit einem anderen Wert."""
|
|
||||||
if other is None:
|
|
||||||
return False
|
|
||||||
assert isinstance(other, Literal), "Can only compare with Literal or MemoryCell"
|
|
||||||
self.compare_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
return self.value == other.value
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
"""Vergleicht den Wert der Speicherzelle mit einem anderen Wert."""
|
|
||||||
if other is None:
|
|
||||||
return True
|
|
||||||
assert isinstance(other, Literal), "Can only compare with Literal or MemoryCell"
|
|
||||||
self.compare_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
return self.value != other.value
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
"""Vergleicht den Wert der Speicherzelle mit einem anderen Wert."""
|
|
||||||
assert isinstance(other, Literal), "Can only compare with Literal or MemoryCell"
|
|
||||||
self.compare_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
return self.value < other.value
|
|
||||||
|
|
||||||
def __le__(self, other):
|
|
||||||
"""Vergleicht den Wert der Speicherzelle mit einem anderen Wert."""
|
|
||||||
assert isinstance(other, Literal), "Can only compare with Literal or MemoryCell"
|
|
||||||
self.compare_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
return self.value <= other.value
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
"""Vergleicht den Wert der Speicherzelle mit einem anderen Wert."""
|
|
||||||
assert isinstance(other, Literal), "Can only compare with Literal or MemoryCell"
|
|
||||||
self.compare_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
return self.value > other.value
|
|
||||||
|
|
||||||
def __ge__(self, other):
|
|
||||||
"""Vergleicht den Wert der Speicherzelle mit einem anderen Wert."""
|
|
||||||
assert isinstance(other, Literal), "Can only compare with Literal or MemoryCell"
|
|
||||||
self.compare_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
return self.value >= other.value
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""Repräsentation des Werts."""
|
|
||||||
return f"{self.value}"
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
"""Repräsentation des Werts für Debugging-Zwecke."""
|
|
||||||
return f"Literal(value={self.value}, reads={self.read_count})"
|
|
||||||
|
|
||||||
def get_read_count(self):
|
|
||||||
"""Gibt zurück, wie oft der Wert gelesen wurde."""
|
|
||||||
return self.read_count
|
|
||||||
|
|
||||||
def __int__(self):
|
|
||||||
"""Gibt den Wert als Integer zurück."""
|
|
||||||
self.read_count += 1
|
|
||||||
return int(self.value)
|
|
||||||
|
|
||||||
def succ(self):
|
|
||||||
return Literal(self.value+1)
|
|
||||||
|
|
||||||
def pred(self):
|
|
||||||
return Literal(self.value-1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
l1 = Literal(5)
|
|
||||||
l2 = Literal(3)
|
|
||||||
print(l1 == l2)
|
|
||||||
print(l1 > l2)
|
|
||||||
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
from utils.literal import Literal
|
|
||||||
from utils.memory_cell import MemoryCell
|
|
||||||
from utils.memory_manager import MemoryManager
|
|
||||||
from utils.project_dir import get_path
|
|
||||||
from random import randint
|
|
||||||
|
|
||||||
class MemoryArray:
|
|
||||||
|
|
||||||
def __init__(self, parm):
|
|
||||||
if isinstance(parm, Literal):
|
|
||||||
self.init_with_size(parm)
|
|
||||||
elif isinstance(parm, list):
|
|
||||||
self.size = len(parm)
|
|
||||||
self.cells = [MemoryCell(value) for value in parm]
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid parameter type")
|
|
||||||
|
|
||||||
def init_with_size(self, size):
|
|
||||||
"""Initialisiert ein Speicherarray mit einer bestimmten Größe."""
|
|
||||||
assert isinstance(size, Literal), "Size must be a Literal or MemoryCell"
|
|
||||||
assert isinstance(size.value, int), "Size must be an int"
|
|
||||||
assert size.value > 0, "Size must be positive"
|
|
||||||
self.size = size.value
|
|
||||||
self.cells = [MemoryCell() for _ in range(self.size)]
|
|
||||||
|
|
||||||
def __getitem__(self, index):
|
|
||||||
"""Gibt den Wert einer Speicherzelle zurück."""
|
|
||||||
assert isinstance(index, Literal), "Index must be a Literal or MemoryCell"
|
|
||||||
assert isinstance(index.value, int), "Index value must be an int"
|
|
||||||
assert 0 <= index.value < self.size, "Index out of bounds"
|
|
||||||
return self.cells[index.value]
|
|
||||||
|
|
||||||
def __setitem__(self, index, value):
|
|
||||||
"""Setzt den Wert einer Speicherzelle."""
|
|
||||||
assert isinstance(index, Literal), "Index must be a Literal or MemoryCell"
|
|
||||||
assert isinstance(index.value, int), "Index value must be an int"
|
|
||||||
assert 0 <= index.value < self.size, "Index out of bounds"
|
|
||||||
assert isinstance(value, Literal), "Value must be a Literal or MemoryCell"
|
|
||||||
self.cells[index.value].set(value.value)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
"""Gibt die Größe des Speicherarrays zurück."""
|
|
||||||
return self.size
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""Gibt eine Liste der Speicherzellen zurück."""
|
|
||||||
return str([cell.value for cell in self.cells])
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
"""Gibt einen Iterator über die Speicherzellen zurück."""
|
|
||||||
return iter(self.cells)
|
|
||||||
|
|
||||||
def indices(self):
|
|
||||||
"""Gibt eine Liste der Indizes der Speicherzellen zurück."""
|
|
||||||
return [Literal(i) for i in range(self.size)]
|
|
||||||
|
|
||||||
def length(self):
|
|
||||||
"""Gibt die Größe des Speicherarrays zurück."""
|
|
||||||
return Literal(self.size)
|
|
||||||
|
|
||||||
def count_compares(self):
|
|
||||||
return sum([cell.compare_count for cell in self.cells])
|
|
||||||
|
|
||||||
|
|
||||||
def reset_counters(self):
|
|
||||||
"""Setzt alle Zähler auf 0 zurück."""
|
|
||||||
for cell in self.cells:
|
|
||||||
cell.reset_counters()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_random_array(count, min_value, max_value):
|
|
||||||
"""Erzeugt ein zufälliges Speicherarray."""
|
|
||||||
size = Literal(count)
|
|
||||||
a = MemoryArray(size)
|
|
||||||
for i in a.indices():
|
|
||||||
a[i] = Literal(randint(min_value, max_value))
|
|
||||||
a.reset_counters()
|
|
||||||
return a
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_sorted_array(count):
|
|
||||||
"""Erzeugt ein sortiertes Speicherarray."""
|
|
||||||
a = MemoryArray(list(range(count)))
|
|
||||||
a.reset_counters()
|
|
||||||
return a
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_array_from_file(filename, limit=None):
|
|
||||||
"""Erzeugt ein Speicherarray aus einer Datei."""
|
|
||||||
filename = get_path(filename)
|
|
||||||
with open(filename) as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
if limit is not None:
|
|
||||||
lines = lines[:limit]
|
|
||||||
size = Literal(len(lines))
|
|
||||||
a = MemoryArray(size)
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
a[Literal(i)] = Literal(int(line))
|
|
||||||
a.reset_counters()
|
|
||||||
return a
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
result = "[ "
|
|
||||||
for cell in self.cells:
|
|
||||||
result += str(cell) + ", "
|
|
||||||
result += "]"
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import random
|
|
||||||
size = Literal(5)
|
|
||||||
a = MemoryArray(size)
|
|
||||||
for i in a.indices():
|
|
||||||
a[i] = Literal(random.randint(1,100))
|
|
||||||
print(a)
|
|
||||||
s = MemoryCell(0)
|
|
||||||
for cell in a.cells:
|
|
||||||
s += cell
|
|
||||||
print(s)
|
|
||||||
print(f"Anzahl der Additionen: {MemoryManager.count_adds()}")
|
|
||||||
|
|
||||||
a = MemoryArray.create_array_from_file("data/seq0.txt")
|
|
||||||
print(a)
|
|
||||||
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
from utils.memory_manager import MemoryManager
|
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
class MemoryCell (Literal):
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
|
||||||
"""Erstellt eine neue Instanz von MemoryCell."""
|
|
||||||
instance = MemoryManager().acquire_cell()
|
|
||||||
if instance is None:
|
|
||||||
instance = super().__new__(cls)
|
|
||||||
MemoryManager().register_cell(instance)
|
|
||||||
return instance
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
MemoryManager().release_cell(self)
|
|
||||||
|
|
||||||
def __init__(self, value=None):
|
|
||||||
"""Initialisiert eine Speicherzelle mit optionalem Startwert."""
|
|
||||||
super().__init__(value)
|
|
||||||
self.write_count = 0
|
|
||||||
self.add_count = 0
|
|
||||||
self.sub_count = 0
|
|
||||||
self.mul_count = 0
|
|
||||||
self.div_count = 0
|
|
||||||
self.bitop_count = 0
|
|
||||||
if value is not None:
|
|
||||||
self.write_count +=1
|
|
||||||
else:
|
|
||||||
self.value = 0
|
|
||||||
|
|
||||||
def reset_counters(self):
|
|
||||||
"""Setzt alle Zähler auf 0 zurück."""
|
|
||||||
super().reset_counters()
|
|
||||||
self.write_count = 0
|
|
||||||
self.add_count = 0
|
|
||||||
self.sub_count = 0
|
|
||||||
self.mul_count = 0
|
|
||||||
self.div_count = 0
|
|
||||||
self.bitop_count = 0
|
|
||||||
|
|
||||||
def set(self, new_value):
|
|
||||||
"""Schreibt einen neuen Wert in die Speicherzelle und erhöht den Schreibzähler."""
|
|
||||||
self.write_count += 1
|
|
||||||
if isinstance(new_value, Literal):
|
|
||||||
self.value = new_value.value
|
|
||||||
else:
|
|
||||||
self.value = new_value
|
|
||||||
|
|
||||||
def add(self, other):
|
|
||||||
"""Addiert den Wert der Speicherzelle mit einem anderen Wert."""
|
|
||||||
self.set((self + other).value)
|
|
||||||
|
|
||||||
def sub(self, other):
|
|
||||||
"""Subtrahiert den Wert der Speicherzelle mit einem anderen Wert."""
|
|
||||||
self.set((self - other).value)
|
|
||||||
|
|
||||||
def mul(self, other):
|
|
||||||
"""Multipliziert den Wert der Speicherzelle mit einem anderen Wert."""
|
|
||||||
self.set((self * other).value)
|
|
||||||
|
|
||||||
def div(self, other):
|
|
||||||
"""Dividiert den Wert der Speicherzelle durch einen anderen Wert."""
|
|
||||||
self.set((self // other).value)
|
|
||||||
|
|
||||||
def modulo(self, other):
|
|
||||||
"""Berechnet den Modulo des Wertes der Speicherzelle durch einen anderen Wert."""
|
|
||||||
self.set((self % other).value)
|
|
||||||
|
|
||||||
def lshift(self, other):
|
|
||||||
"""Verschiebt den Wert der Speicherzelle um eine bestimmte Anzahl von Bits nach links."""
|
|
||||||
assert isinstance(other, Literal), "Can only lshift Literal or MemoryCell by MemoryCell"
|
|
||||||
self.bitop_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
self.write_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
self.value <<= other.value
|
|
||||||
|
|
||||||
def rshift(self, other):
|
|
||||||
"""Verschiebt den Wert der Speicherzelle um eine bestimmte Anzahl von Bits nach rechts."""
|
|
||||||
assert isinstance(other, Literal), "Can only rshift Literal or MemoryCell by MemoryCell"
|
|
||||||
self.bitop_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
self.write_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
self.value >>= other.value
|
|
||||||
|
|
||||||
def and_op(self, other):
|
|
||||||
"""Führt ein Bitweise AND auf den Wert der Speicherzelle mit einem anderen Wert aus."""
|
|
||||||
assert isinstance(other, Literal), "Can only and Literal or MemoryCell with MemoryCell"
|
|
||||||
self.bitop_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
self.write_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
self.value &= other.value
|
|
||||||
|
|
||||||
def or_op(self, other):
|
|
||||||
"""Führt ein Bitweise OR auf den Wert der Speicherzelle mit einem anderen Wert aus."""
|
|
||||||
assert isinstance(other, Literal), "Can only or Literal or MemoryCell with MemoryCell"
|
|
||||||
self.bitop_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
self.write_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
self.value |= other.value
|
|
||||||
|
|
||||||
def xor_op(self, other):
|
|
||||||
"""Führt ein Bitweise XOR auf den Wert der Speicherzelle mit einem anderen Wert aus."""
|
|
||||||
assert isinstance(other, Literal), "Can only xor Literal or MemoryCell with MemoryCell"
|
|
||||||
self.bitop_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
self.write_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
self.value ^= other.value
|
|
||||||
|
|
||||||
def get_write_count(self):
|
|
||||||
"""Gibt zurück, wie oft der Wert geschrieben wurde."""
|
|
||||||
return self.write_count
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
"""Repräsentation der Speicherzelle für Debugging-Zwecke."""
|
|
||||||
return f"MemoryCell(value={self.value}, reads={self.read_count}, writes={self.write_count})"
|
|
||||||
|
|
||||||
def __add__(self, other):
|
|
||||||
assert isinstance(other, Literal), "Can only add Literal or MemoryCell to MemoryCell"
|
|
||||||
self.add_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
return Literal(self.value + other.value)
|
|
||||||
|
|
||||||
def __sub__(self, other):
|
|
||||||
assert isinstance(other, Literal), "Can only add Literal or MemoryCell to MemoryCell"
|
|
||||||
self.sub_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
return Literal(self.value - other.value)
|
|
||||||
|
|
||||||
def __mul__(self, other):
|
|
||||||
assert isinstance(other, Literal), "Can only mul Literal or MemoryCell with MemoryCell"
|
|
||||||
self.mul_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
return Literal(self.value * other.value)
|
|
||||||
|
|
||||||
def __truediv__(self, other):
|
|
||||||
assert isinstance(other, Literal), "Can only div Literal or MemoryCell by MemoryCell"
|
|
||||||
self.div_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
return Literal(self.value / other.value)
|
|
||||||
|
|
||||||
def __floordiv__(self, other):
|
|
||||||
assert isinstance(other, Literal), "Can only div Literal or MemoryCell by MemoryCell"
|
|
||||||
self.div_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
return Literal(self.value // other.value)
|
|
||||||
|
|
||||||
def __mod__(self, other):
|
|
||||||
assert isinstance(other, Literal), "Can only div Literal or MemoryCell by MemoryCell"
|
|
||||||
self.div_count += 1
|
|
||||||
self.read_count += 1
|
|
||||||
other.read_count += 1
|
|
||||||
return Literal(self.value % other.value)
|
|
||||||
|
|
||||||
def __iadd__(self, other):
|
|
||||||
self.add(other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __isub__(self, other):
|
|
||||||
self.sub(other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __imul__(self, other):
|
|
||||||
self.mul(other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __itruediv__(self, other):
|
|
||||||
self.set(self // other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __ifloordiv__(self, other):
|
|
||||||
self.div(other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
a = MemoryCell(5)
|
|
||||||
b = MemoryCell(3)
|
|
||||||
a += b
|
|
||||||
print(f"Ergebnis: {a}")
|
|
||||||
print(f"a wurde {a.get_read_count()} mal gelesen und {a.get_write_count()} mal geschrieben.")
|
|
||||||
print(f"b wurde {b.get_read_count()} mal gelesen und {b.get_write_count()} mal geschrieben.")
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
import matplotlib.pyplot as plt
|
|
||||||
import queue
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryManager:
|
|
||||||
|
|
||||||
_instance = None
|
|
||||||
stats = {}
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
|
||||||
"""Erstellt eine einzige Instanz von MemoryManager."""
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super().__new__(cls)
|
|
||||||
cls._instance._initialize() # Eigene Init-Methode, damit __init__ nicht mehrfach läuft
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def _initialize(self):
|
|
||||||
"""Initialisiert die Speicherverwaltung (einmalig)."""
|
|
||||||
self.cells = []
|
|
||||||
self._pool = queue.Queue()
|
|
||||||
self._finalizers = {}
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def count_cells():
|
|
||||||
return len(MemoryManager().cells)
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def count_reads():
|
|
||||||
return sum([cell.read_count for cell in MemoryManager().cells])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def count_writes():
|
|
||||||
return sum([cell.write_count for cell in MemoryManager().cells])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def count_compares():
|
|
||||||
return sum([cell.compare_count for cell in MemoryManager().cells])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def count_adds():
|
|
||||||
return sum([cell.add_count for cell in MemoryManager().cells])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def count_subs():
|
|
||||||
return sum([cell.sub_count for cell in MemoryManager().cells])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def count_muls():
|
|
||||||
return sum([cell.mul_count for cell in MemoryManager().cells])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def count_divs():
|
|
||||||
return sum([cell.div_count for cell in MemoryManager().cells])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def count_bitops():
|
|
||||||
return sum([cell.bitop_count for cell in MemoryManager().cells])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def reset():
|
|
||||||
manager = MemoryManager()
|
|
||||||
for cell in manager.cells:
|
|
||||||
cell.reset_counters()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def purge():
|
|
||||||
MemoryManager._instance = None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def save_stats(count):
|
|
||||||
data = { "cells": MemoryManager.count_cells(),
|
|
||||||
"reads": MemoryManager.count_reads(),
|
|
||||||
"writes": MemoryManager.count_writes(),
|
|
||||||
"compares": MemoryManager.count_compares(),
|
|
||||||
"adds": MemoryManager.count_adds(),
|
|
||||||
"subs": MemoryManager.count_subs(),
|
|
||||||
"muls": MemoryManager.count_muls(),
|
|
||||||
"divs": MemoryManager.count_divs(),
|
|
||||||
"bitops": MemoryManager.count_bitops() }
|
|
||||||
MemoryManager.stats[count] = data
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def plot_stats(labels):
|
|
||||||
data = MemoryManager.stats
|
|
||||||
x = list(data.keys())
|
|
||||||
|
|
||||||
fig, axes = plt.subplots(len(labels), 1, figsize=(8, 4 * len(labels)), sharex=True)
|
|
||||||
|
|
||||||
if len(labels) == 1:
|
|
||||||
axes = [axes] # Falls nur ein Plot vorhanden ist, in eine Liste umwandeln
|
|
||||||
|
|
||||||
for ax, l in zip(axes, labels):
|
|
||||||
y = [data[k][l] for k in x]
|
|
||||||
ax.plot(x, y, label=l)
|
|
||||||
ax.set_ylabel(l)
|
|
||||||
ax.legend()
|
|
||||||
|
|
||||||
plt.xlabel("n")
|
|
||||||
plt.show()
|
|
||||||
|
|
||||||
|
|
||||||
def acquire_cell(self):
|
|
||||||
try:
|
|
||||||
return self._pool.get_nowait()
|
|
||||||
except queue.Empty:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def register_cell(self, cell):
|
|
||||||
self.cells.append(cell)
|
|
||||||
|
|
||||||
def release_cell(self, cell):
|
|
||||||
self._pool.put(cell)
|
|
||||||
|
|
||||||
|
|
||||||
class Testcell:
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
|
||||||
instance = MemoryManager().acquire_cell()
|
|
||||||
if instance is None:
|
|
||||||
instance = super().__new__(cls)
|
|
||||||
MemoryManager().register_cell(instance)
|
|
||||||
return instance
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
MemoryManager().release_cell(self)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
|
|
||||||
# Einfaches Anlegen einer Zelle
|
|
||||||
a = Testcell()
|
|
||||||
print(MemoryManager.count_cells())
|
|
||||||
|
|
||||||
# Anlegen einer Zelle und Beenden des Scopes
|
|
||||||
with Testcell() as b:
|
|
||||||
print(MemoryManager.count_cells())
|
|
||||||
|
|
||||||
print(MemoryManager.count_cells())
|
|
||||||
|
|
||||||
# Reuse einer Zelle
|
|
||||||
c = Testcell()
|
|
||||||
print(MemoryManager.count_cells())
|
|
||||||
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
# a generator that yields items instead of returning a list
|
|
||||||
def mrange(parm1, parm2=None, parm3=None):
|
|
||||||
if parm2 is None:
|
|
||||||
start = 0
|
|
||||||
stop = int(parm1)
|
|
||||||
step = 1
|
|
||||||
elif parm3 is None:
|
|
||||||
start = int(parm1)
|
|
||||||
stop = int(parm2)
|
|
||||||
step = 1
|
|
||||||
else:
|
|
||||||
start = int(parm1)
|
|
||||||
stop = int(parm2)
|
|
||||||
step = int(parm3)
|
|
||||||
num = start
|
|
||||||
if step > 0:
|
|
||||||
while num < stop:
|
|
||||||
yield Literal(num)
|
|
||||||
num += step
|
|
||||||
else:
|
|
||||||
while num > stop:
|
|
||||||
yield Literal(num)
|
|
||||||
num += step
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
for l in mrange(10):
|
|
||||||
print(l)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
def get_path(filename) -> Path:
|
|
||||||
this_dir = Path(__file__).resolve().parent
|
|
||||||
project_dir = this_dir.parent
|
|
||||||
return project_dir / filename
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
filename = get_path("data/seq0.txt")
|
|
||||||
print(filename)
|
|
||||||
print(filename.resolve())
|
|
||||||
print(filename.is_file())
|
|
||||||
print(filename.exists())
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
from unittest import TestCase
|
|
||||||
from utils.literal import Literal
|
|
||||||
from utils.memory_array import MemoryArray
|
|
||||||
import random
|
|
||||||
|
|
||||||
class TestMemoryArray(TestCase):
|
|
||||||
|
|
||||||
def test_create_array(self):
|
|
||||||
l = random.randint(5,10)
|
|
||||||
size = Literal(l)
|
|
||||||
a = MemoryArray(size)
|
|
||||||
self.assertEqual(len(a), l)
|
|
||||||
|
|
||||||
def test_set_item(self):
|
|
||||||
l = random.randint(5,10)
|
|
||||||
size = Literal(l)
|
|
||||||
a = MemoryArray(size)
|
|
||||||
i = Literal(random.randint(0,l-1))
|
|
||||||
v = Literal(random.randint(1,100))
|
|
||||||
a[i] = v
|
|
||||||
self.assertEqual(a[i].value, v.value)
|
|
||||||
|
|
||||||
def test_get_item(self):
|
|
||||||
l = random.randint(5,10)
|
|
||||||
values = [random.randint(1,100) for _ in range(l)]
|
|
||||||
a = MemoryArray(values)
|
|
||||||
for pos, i in enumerate(a.indices()):
|
|
||||||
self.assertEqual(a[i].value, values[pos])
|
|
||||||
|
|
||||||
def test_reset_counters(self):
|
|
||||||
l = random.randint(5,10)
|
|
||||||
values = [random.randint(1,100) for _ in range(l)]
|
|
||||||
a = MemoryArray(values)
|
|
||||||
for i in a.indices():
|
|
||||||
self.assertEqual(a[i].write_count, 1)
|
|
||||||
a.reset_counters()
|
|
||||||
for i in a.indices():
|
|
||||||
self.assertEqual(a[i].write_count, 0)
|
|
||||||
|
|
||||||
def test_create_random_array(self):
|
|
||||||
a = MemoryArray.create_random_array(10, 1, 100)
|
|
||||||
self.assertEqual(len(a), 10)
|
|
||||||
|
|
||||||
def test_create_array_from_file(self):
|
|
||||||
a = MemoryArray.create_array_from_file("data/seq0.txt")
|
|
||||||
self.assertEqual(len(a), 14)
|
|
||||||
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
from unittest import TestCase
|
|
||||||
from utils.memory_cell import MemoryCell
|
|
||||||
from utils.literal import Literal
|
|
||||||
import random
|
|
||||||
|
|
||||||
|
|
||||||
class TestMemoryCell(TestCase):
|
|
||||||
|
|
||||||
def test_create_cell(self):
|
|
||||||
v = random.randint(1, 100)
|
|
||||||
cell = MemoryCell(v)
|
|
||||||
self.assertEqual(cell.value, v)
|
|
||||||
self.assertEqual(cell.read_count, 0)
|
|
||||||
self.assertEqual(cell.write_count, 1)
|
|
||||||
self.assertEqual(cell.add_count, 0)
|
|
||||||
self.assertEqual(cell.sub_count, 0)
|
|
||||||
self.assertEqual(cell.mul_count, 0)
|
|
||||||
self.assertEqual(cell.div_count, 0)
|
|
||||||
self.assertEqual(cell.bitop_count, 0)
|
|
||||||
|
|
||||||
def test_cast_cell(self):
|
|
||||||
v = random.randint(1, 100)
|
|
||||||
cell = MemoryCell(v)
|
|
||||||
self.assertEqual(int(cell), v)
|
|
||||||
self.assertEqual(cell.read_count, 1)
|
|
||||||
self.assertEqual(cell.write_count, 1)
|
|
||||||
self.assertEqual(cell.add_count, 0)
|
|
||||||
self.assertEqual(cell.sub_count, 0)
|
|
||||||
self.assertEqual(cell.mul_count, 0)
|
|
||||||
self.assertEqual(cell.div_count, 0)
|
|
||||||
self.assertEqual(cell.bitop_count, 0)
|
|
||||||
|
|
||||||
def test_add(self):
|
|
||||||
v1 = random.randint(1, 100)
|
|
||||||
v2 = random.randint(1, 100)
|
|
||||||
|
|
||||||
# "in place" Addition zweier MemoryCells
|
|
||||||
cell1 = MemoryCell(v1)
|
|
||||||
cell2 = MemoryCell(v2)
|
|
||||||
cell1 += cell2
|
|
||||||
self.assertEqual(cell1.value, v1 + v2)
|
|
||||||
self.assertEqual(cell1.add_count, 1)
|
|
||||||
self.assertEqual(cell1.read_count, 1)
|
|
||||||
self.assertEqual(cell2.read_count, 1)
|
|
||||||
self.assertTrue(isinstance(cell1, MemoryCell))
|
|
||||||
self.assertTrue(isinstance(cell2, MemoryCell))
|
|
||||||
|
|
||||||
# Freie Addition zweier MemoryCells
|
|
||||||
cell1 = MemoryCell(v1)
|
|
||||||
cell2 = MemoryCell(v2)
|
|
||||||
result = cell1 + cell2
|
|
||||||
|
|
||||||
self.assertEqual(result.value, v1 + v2)
|
|
||||||
self.assertEqual(cell1.add_count, 1)
|
|
||||||
self.assertEqual(cell1.read_count, 1)
|
|
||||||
self.assertEqual(cell2.read_count, 1)
|
|
||||||
self.assertTrue(isinstance(result, Literal))
|
|
||||||
|
|
||||||
def test_sub(self):
|
|
||||||
v1 = random.randint(1, 100)
|
|
||||||
v2 = random.randint(1, 100)
|
|
||||||
|
|
||||||
# "in place" Subtraktion zweier MemoryCells
|
|
||||||
cell1 = MemoryCell(v1)
|
|
||||||
cell2 = MemoryCell(v2)
|
|
||||||
cell1 -= cell2
|
|
||||||
|
|
||||||
self.assertEqual(cell1.value, v1 - v2)
|
|
||||||
self.assertEqual(cell1.sub_count, 1)
|
|
||||||
self.assertEqual(cell1.read_count, 1)
|
|
||||||
self.assertEqual(cell2.read_count, 1)
|
|
||||||
self.assertTrue(isinstance(cell1, MemoryCell))
|
|
||||||
self.assertTrue(isinstance(cell2, MemoryCell))
|
|
||||||
|
|
||||||
# Freie Subtraktion zweier MemoryCells
|
|
||||||
cell1 = MemoryCell(v1)
|
|
||||||
cell2 = MemoryCell(v2)
|
|
||||||
result = cell1 - cell2
|
|
||||||
self.assertEqual(result.value, v1 - v2)
|
|
||||||
self.assertEqual(cell1.sub_count, 1)
|
|
||||||
self.assertEqual(cell1.read_count, 1)
|
|
||||||
self.assertEqual(cell2.read_count, 1)
|
|
||||||
self.assertTrue(isinstance(result, Literal))
|
|
||||||
|
|
||||||
def test_mul(self):
|
|
||||||
v1 = random.randint(1, 100)
|
|
||||||
v2 = random.randint(1, 100)
|
|
||||||
|
|
||||||
# "in place" Multiplikation zweier MemoryCells
|
|
||||||
cell1 = MemoryCell(v1)
|
|
||||||
cell2 = MemoryCell(v2)
|
|
||||||
cell1 *= cell2
|
|
||||||
|
|
||||||
self.assertEqual(cell1.value, v1 * v2)
|
|
||||||
self.assertEqual(cell1.mul_count, 1)
|
|
||||||
self.assertEqual(cell1.read_count, 1)
|
|
||||||
self.assertEqual(cell2.read_count, 1)
|
|
||||||
self.assertTrue(isinstance(cell1, MemoryCell))
|
|
||||||
self.assertTrue(isinstance(cell2, MemoryCell))
|
|
||||||
|
|
||||||
# Freie Multiplikation zweier MemoryCells
|
|
||||||
cell1 = MemoryCell(v1)
|
|
||||||
cell2 = MemoryCell(v2)
|
|
||||||
result = cell1 * cell2
|
|
||||||
self.assertEqual(result.value, v1 * v2)
|
|
||||||
self.assertEqual(cell1.mul_count, 1)
|
|
||||||
self.assertEqual(cell1.read_count, 1)
|
|
||||||
self.assertEqual(cell2.read_count, 1)
|
|
||||||
self.assertTrue(isinstance(result, Literal))
|
|
||||||
|
|
||||||
def test_div(self):
|
|
||||||
v1 = random.randint(1, 100)
|
|
||||||
v2 = random.randint(1, 100)
|
|
||||||
|
|
||||||
# "in place" Division zweier MemoryCells
|
|
||||||
cell1 = MemoryCell(v1)
|
|
||||||
cell2 = MemoryCell(v2)
|
|
||||||
cell1 //= cell2
|
|
||||||
|
|
||||||
self.assertEqual(cell1.value, v1 // v2)
|
|
||||||
self.assertEqual(cell1.div_count, 1)
|
|
||||||
self.assertEqual(cell1.read_count, 1)
|
|
||||||
self.assertEqual(cell2.read_count, 1)
|
|
||||||
self.assertTrue(isinstance(cell1, MemoryCell))
|
|
||||||
self.assertTrue(isinstance(cell2, MemoryCell))
|
|
||||||
|
|
||||||
# Freie Division zweier MemoryCells
|
|
||||||
cell1 = MemoryCell(v1)
|
|
||||||
cell2 = MemoryCell(v2)
|
|
||||||
result = cell1 // cell2
|
|
||||||
self.assertEqual(result.value, v1 // v2)
|
|
||||||
self.assertEqual(cell1.div_count, 1)
|
|
||||||
self.assertEqual(cell1.read_count, 1)
|
|
||||||
self.assertEqual(cell2.read_count, 1)
|
|
||||||
self.assertTrue(isinstance(result, Literal))
|
|
||||||
|
|
||||||
def test_reset_counters(self):
|
|
||||||
v1 = random.randint(1, 100)
|
|
||||||
v2 = random.randint(1, 100)
|
|
||||||
cell = MemoryCell(v1)
|
|
||||||
cell += Literal(v2)
|
|
||||||
|
|
||||||
self.assertEqual(cell.value, v1+v2)
|
|
||||||
self.assertEqual(cell.read_count, 1)
|
|
||||||
self.assertEqual(cell.add_count, 1)
|
|
||||||
self.assertEqual(cell.write_count, 2)
|
|
||||||
|
|
||||||
cell.reset_counters()
|
|
||||||
|
|
||||||
self.assertEqual(cell.value, v1+v2)
|
|
||||||
self.assertEqual(cell.read_count, 0)
|
|
||||||
self.assertEqual(cell.add_count, 0)
|
|
||||||
self.assertEqual(cell.write_count, 0)
|
|
||||||
|
|
||||||
|
|
||||||
def test_set(self):
|
|
||||||
v1 = random.randint(1, 100)
|
|
||||||
v2 = random.randint(1, 100)
|
|
||||||
|
|
||||||
cell = MemoryCell(v1)
|
|
||||||
cell.set(v2)
|
|
||||||
|
|
||||||
self.assertEqual(cell.value, v2)
|
|
||||||
self.assertEqual(cell.read_count, 0)
|
|
||||||
self.assertEqual(cell.write_count, 2)
|
|
||||||
|
|
||||||
Whitespace-only changes.
@@ -1,13 +1,13 @@
|
|||||||
from utils.memory_cell import MemoryCell
|
from utils import AlgoContext, Int
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
x = MemoryCell(int(input("Erste Zahl: ")))
|
ctx = AlgoContext()
|
||||||
y = MemoryCell(int(input("Zweite Zahl: ")))
|
x = Int(int(input("Erste Zahl: ")), ctx)
|
||||||
|
y = Int(int(input("Zweite Zahl: ")), ctx)
|
||||||
|
|
||||||
while x > Literal(0):
|
while x > 0:
|
||||||
if x < y:
|
if x < y:
|
||||||
x, y = y, x
|
x, y = y, x
|
||||||
x -= y
|
x -= y
|
||||||
print(y)
|
print(y)
|
||||||
|
|
||||||
print(f"Insgesamt gab es {x.sub_count + y.sub_count} Subtraktionen.")
|
print(f"Insgesamt gab es {ctx.subtractions} Subtraktionen.")
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_array import Array
|
||||||
|
from utils.algo_int import Int
|
||||||
|
from utils.algo_range import Range
|
||||||
|
|
||||||
|
|
||||||
|
def max_sequence_1(z: Array, ctx: AlgoContext):
|
||||||
|
"""O(n³) – dreifach verschachtelte Schleife."""
|
||||||
|
n = len(z)
|
||||||
|
m = Int(float('-inf'), ctx)
|
||||||
|
s = Int(0, ctx)
|
||||||
|
l = Int(0, ctx)
|
||||||
|
r = Int(0, ctx)
|
||||||
|
|
||||||
|
for i in Range(n):
|
||||||
|
for j in Range(int(i), n):
|
||||||
|
s.set(0)
|
||||||
|
for k in Range(int(i), int(j)):
|
||||||
|
s += z[k]
|
||||||
|
if s > m:
|
||||||
|
m.set(s)
|
||||||
|
l.set(Int(int(i), ctx))
|
||||||
|
r.set(Int(int(j), ctx))
|
||||||
|
return m, l, r
|
||||||
|
|
||||||
|
|
||||||
|
def max_sequence_2(z: Array, ctx: AlgoContext):
|
||||||
|
"""O(n²) – doppelt verschachtelte Schleife."""
|
||||||
|
n = len(z)
|
||||||
|
m = Int(float('-inf'), ctx)
|
||||||
|
s = Int(0, ctx)
|
||||||
|
left = Int(0, ctx)
|
||||||
|
right = Int(0, ctx)
|
||||||
|
|
||||||
|
for i in Range(n):
|
||||||
|
s.set(0)
|
||||||
|
for j in Range(int(i), n):
|
||||||
|
s += z[j]
|
||||||
|
if s > m:
|
||||||
|
m.set(s)
|
||||||
|
left.set(Int(int(i), ctx))
|
||||||
|
right.set(Int(int(j), ctx))
|
||||||
|
return m, left, right
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def max_sequence_4(z: Array, ctx: AlgoContext):
|
||||||
|
"""O(n) – Kadane's Algorithmus."""
|
||||||
|
n = len(z)
|
||||||
|
m = Int(float('-inf'), ctx)
|
||||||
|
cur_sum = Int(0, ctx)
|
||||||
|
cur_left = Int(0, ctx)
|
||||||
|
left = Int(0, ctx)
|
||||||
|
right = Int(0, ctx)
|
||||||
|
|
||||||
|
for i in Range(n):
|
||||||
|
cur_sum += z[i]
|
||||||
|
if cur_sum > m:
|
||||||
|
m.set(cur_sum)
|
||||||
|
left.set(cur_left)
|
||||||
|
right.set(Int(int(i), ctx))
|
||||||
|
if cur_sum < 0:
|
||||||
|
cur_sum.set(0)
|
||||||
|
cur_left.set(Int(int(i) + 1, ctx))
|
||||||
|
|
||||||
|
return m, left, right
|
||||||
|
|
||||||
|
|
||||||
|
def example(max_sequence_func):
|
||||||
|
ctx = AlgoContext()
|
||||||
|
data = [-59, 52, 46, 14, -50, 58, -87, -77, 34, 15]
|
||||||
|
print(data)
|
||||||
|
z = Array(data, ctx)
|
||||||
|
m, l, r = max_sequence_func(z, ctx)
|
||||||
|
print(m, l, r)
|
||||||
|
|
||||||
|
|
||||||
|
def seq(filename, max_sequence_func):
|
||||||
|
ctx = AlgoContext()
|
||||||
|
z = Array.from_file(filename, ctx)
|
||||||
|
m, l, r = max_sequence_func(z, ctx)
|
||||||
|
print(m, l, r)
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_complexity(max_sequence_func, sizes):
|
||||||
|
ctx = AlgoContext()
|
||||||
|
for size in sizes:
|
||||||
|
ctx.reset()
|
||||||
|
z = Array.random(size, -100, 100, ctx)
|
||||||
|
max_sequence_func(z, ctx)
|
||||||
|
ctx.save_stats(size)
|
||||||
|
|
||||||
|
ctx.plot_stats(["additions"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
example(max_sequence_2)
|
||||||
|
for filename in ["data/seq0.txt", "data/seq1.txt"]:
|
||||||
|
print(filename)
|
||||||
|
seq(filename, max_sequence_2)
|
||||||
|
analyze_complexity(max_sequence_2, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
from utils.algo_game import Game
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_array import Array
|
||||||
from bubble_sorting import bubble_sort_stepwise
|
from bubble_sorting import bubble_sort_stepwise
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
class BubbleGame(Game):
|
class BubbleGame(Game):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Bubble Game", fps=60, size=(400, 400))
|
super().__init__("Bubble Game", fps=60, size=(400, 400))
|
||||||
random.seed()
|
random.seed()
|
||||||
l =list(range(1, 101))
|
l = list(range(1, 101))
|
||||||
random.shuffle(l)
|
random.shuffle(l)
|
||||||
self.z = MemoryArray(l)
|
self.ctx = AlgoContext()
|
||||||
|
self.z = Array(l, self.ctx)
|
||||||
self.finished = False
|
self.finished = False
|
||||||
self.sort_generator = bubble_sort_stepwise(self.z)
|
self.sort_generator = bubble_sort_stepwise(self.z, self.ctx)
|
||||||
|
|
||||||
def update_game(self):
|
def update_game(self):
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
@@ -29,7 +32,7 @@ class BubbleGame(Game):
|
|||||||
def draw_game(self):
|
def draw_game(self):
|
||||||
self.screen.fill(WHITE)
|
self.screen.fill(WHITE)
|
||||||
for i, cell in enumerate(self.z):
|
for i, cell in enumerate(self.z):
|
||||||
x = 50 + i*3
|
x = 50 + i * 3
|
||||||
y = 350 - cell.value * 3
|
y = 350 - cell.value * 3
|
||||||
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
||||||
super().draw_game()
|
super().draw_game()
|
||||||
@@ -38,4 +41,3 @@ class BubbleGame(Game):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
b = BubbleGame()
|
b = BubbleGame()
|
||||||
b.run()
|
b.run()
|
||||||
|
|
||||||
@@ -1,80 +1,79 @@
|
|||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_range import Range
|
||||||
from utils.memory_range import mrange
|
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
def bubble_sort_stepwise(z: MemoryArray):
|
|
||||||
n = z.length()
|
def bubble_sort_stepwise(z: Array, ctx: AlgoContext):
|
||||||
for i in mrange(n.pred()):
|
"""
|
||||||
for j in mrange(n.pred(), i, -1):
|
Bubble Sort – schrittweise Variante (Generator).
|
||||||
if z[j.pred()] > z[j]:
|
|
||||||
swap(z, j, j.pred())
|
Gibt nach jedem Tausch den aktuellen Array-Zustand zurück.
|
||||||
|
Wird von bubble_game.py für die Visualisierung verwendet.
|
||||||
|
"""
|
||||||
|
n = len(z)
|
||||||
|
for i in Range(n - 1):
|
||||||
|
for j in Range(n - 1, i, -1):
|
||||||
|
if z[j - 1] > z[j]:
|
||||||
|
z.swap(j - 1, j)
|
||||||
yield z
|
yield z
|
||||||
|
|
||||||
|
|
||||||
def bubble_sort2_stepwise(z: MemoryArray):
|
def bubble_sort2_stepwise(z: Array, ctx: AlgoContext):
|
||||||
n = MemoryCell(z.length())
|
"""
|
||||||
true = Literal(1)
|
Optimierter Bubble Sort mit Frühausstieg – schrittweise Variante.
|
||||||
false = Literal(0)
|
|
||||||
sortiert = MemoryCell()
|
Bricht ab, wenn in einem Durchlauf kein Tausch stattgefunden hat.
|
||||||
|
"""
|
||||||
|
n = len(z)
|
||||||
while True:
|
while True:
|
||||||
sortiert.set(true)
|
swapped = False
|
||||||
for i in mrange(n.pred()):
|
for i in Range(n - 1):
|
||||||
if z[i] > z[i.succ()]:
|
if z[i] > z[i + 1]:
|
||||||
swap(z, i, i.succ())
|
z.swap(i, i + 1)
|
||||||
sortiert.set(false)
|
swapped = True
|
||||||
yield z
|
yield z
|
||||||
n -= Literal(1)
|
n -= 1
|
||||||
if sortiert == true or n <= Literal(1):
|
if not swapped or n <= 1:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def bubble_sort(z: MemoryArray):
|
def bubble_sort(z: Array, ctx: AlgoContext):
|
||||||
sort_generator = bubble_sort_stepwise(z)
|
"""Bubble Sort – vollständige Ausführung ohne Visualisierung."""
|
||||||
while True:
|
for _ in bubble_sort_stepwise(z, ctx):
|
||||||
try:
|
pass
|
||||||
next(sort_generator)
|
|
||||||
except StopIteration:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def bubble_sort2(z: MemoryArray):
|
def bubble_sort2(z: Array, ctx: AlgoContext):
|
||||||
sort_generator = bubble_sort2_stepwise(z)
|
"""Optimierter Bubble Sort – vollständige Ausführung ohne Visualisierung."""
|
||||||
while True:
|
for _ in bubble_sort2_stepwise(z, ctx):
|
||||||
try:
|
pass
|
||||||
next(sort_generator)
|
|
||||||
except StopIteration:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def sort_file(filename, sort_func):
|
|
||||||
z = MemoryArray.create_array_from_file(filename)
|
|
||||||
sort_func(z)
|
|
||||||
return z
|
|
||||||
|
|
||||||
def analyze_complexity(sort_func, sizes, presorted=False):
|
def analyze_complexity(sort_func, sizes, presorted=False):
|
||||||
"""
|
"""
|
||||||
Analysiert die Komplexität einer Sortierfunktion.
|
Analysiert die Komplexität einer Sortierfunktion über mehrere Eingabegrößen.
|
||||||
|
|
||||||
:param sort_func: Die Funktion, die analysiert wird.
|
Parameters
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
----------
|
||||||
|
sort_func : callable
|
||||||
|
Signatour: sort_func(z: Array, ctx: AlgoContext)
|
||||||
|
sizes : list[int]
|
||||||
|
Eingabegrößen für die Analyse.
|
||||||
|
presorted : bool
|
||||||
|
True → sortiertes Eingabe-Array (Best-Case-Analyse).
|
||||||
"""
|
"""
|
||||||
|
ctx = AlgoContext()
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
if presorted:
|
if presorted:
|
||||||
random_array = MemoryArray.create_sorted_array(size)
|
z = Array.sorted(size, ctx)
|
||||||
else:
|
else:
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
sort_func(random_array)
|
sort_func(z, ctx)
|
||||||
MemoryManager.save_stats(size)
|
ctx.save_stats(size)
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares", "writes"])
|
ctx.plot_stats(["comparisons", "writes"])
|
||||||
|
|
||||||
def swap(z: MemoryArray, i: int, j: int):
|
|
||||||
tmp = z[Literal(i)].value
|
|
||||||
z[Literal(i)] = z[Literal(j)]
|
|
||||||
z[Literal(j)].set(tmp)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
analyze_complexity(bubble_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
analyze_complexity(bubble_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
from utils.algo_game import Game
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_array import Array
|
||||||
from insert_sorting import insert_sort_stepwise
|
from insert_sorting import insert_sort_stepwise
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
class InsertGame(Game):
|
class InsertGame(Game):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Insert Game", fps=60, size=(400, 400))
|
super().__init__("Insert Game", fps=60, size=(400, 400))
|
||||||
random.seed()
|
random.seed()
|
||||||
l =list(range(1, 101))
|
l = list(range(1, 101))
|
||||||
random.shuffle(l)
|
random.shuffle(l)
|
||||||
self.z = MemoryArray(l)
|
self.ctx = AlgoContext()
|
||||||
|
self.z = Array(l, self.ctx)
|
||||||
self.finished = False
|
self.finished = False
|
||||||
self.sort_generator = insert_sort_stepwise(self.z)
|
self.sort_generator = insert_sort_stepwise(self.z, self.ctx)
|
||||||
|
|
||||||
def update_game(self):
|
def update_game(self):
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
@@ -29,7 +32,7 @@ class InsertGame(Game):
|
|||||||
def draw_game(self):
|
def draw_game(self):
|
||||||
self.screen.fill(WHITE)
|
self.screen.fill(WHITE)
|
||||||
for i, cell in enumerate(self.z):
|
for i, cell in enumerate(self.z):
|
||||||
x = 50 + i*3
|
x = 50 + i * 3
|
||||||
y = 350 - cell.value * 3
|
y = 350 - cell.value * 3
|
||||||
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
||||||
super().draw_game()
|
super().draw_game()
|
||||||
@@ -38,4 +41,3 @@ class InsertGame(Game):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
b = InsertGame()
|
b = InsertGame()
|
||||||
b.run()
|
b.run()
|
||||||
|
|
||||||
@@ -1,61 +1,49 @@
|
|||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_int import Int
|
||||||
from utils.memory_range import mrange
|
from utils.algo_range import Range
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
def insert_sort_stepwise(z: MemoryArray):
|
|
||||||
n = z.length()
|
def insert_sort_stepwise(z: Array, ctx: AlgoContext):
|
||||||
j = MemoryCell()
|
"""
|
||||||
elem = MemoryCell()
|
Insertion Sort – schrittweise Variante (Generator).
|
||||||
for i in mrange(n):
|
|
||||||
elem.set(z[i])
|
Gibt nach jedem Einfügevorgang den aktuellen Array-Zustand zurück.
|
||||||
j.set(i)
|
"""
|
||||||
while j > Literal(0) and z[j.pred()] > elem:
|
n = len(z)
|
||||||
z[j].set(z[j.pred()])
|
elem = Int(0, ctx) # Zwischenregister für das einzufügende Element
|
||||||
j -= Literal(1)
|
|
||||||
|
for i in Range(n):
|
||||||
|
elem.set(z[i]) # 1 read + 1 write
|
||||||
|
j = Int(int(i), ctx)
|
||||||
|
while j > 0 and z[j - 1] > elem:
|
||||||
|
z[j] = z[j - 1] # 1 read + 1 write
|
||||||
|
j -= 1
|
||||||
yield z
|
yield z
|
||||||
z[j].set(elem)
|
z[j] = elem # 1 read + 1 write
|
||||||
yield z
|
yield z
|
||||||
|
|
||||||
|
|
||||||
def insert_sort(z: MemoryArray):
|
def insert_sort(z: Array, ctx: AlgoContext):
|
||||||
sort_generator = insert_sort_stepwise(z)
|
"""Insertion Sort – vollständige Ausführung ohne Visualisierung."""
|
||||||
while True:
|
for _ in insert_sort_stepwise(z, ctx):
|
||||||
try:
|
pass
|
||||||
next(sort_generator)
|
|
||||||
except StopIteration:
|
|
||||||
break
|
|
||||||
|
|
||||||
def sort_file(filename, sort_func):
|
|
||||||
z = MemoryArray.create_array_from_file(filename)
|
|
||||||
sort_func(z)
|
|
||||||
return z
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sort_func, sizes, presorted=False):
|
def analyze_complexity(sort_func, sizes, presorted=False):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität einer Sortierfunktion.
|
|
||||||
|
|
||||||
:param sort_func: Die Funktion, die analysiert wird.
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
if presorted:
|
if presorted:
|
||||||
random_array = MemoryArray.create_sorted_array(size)
|
z = Array.sorted(size, ctx)
|
||||||
else:
|
else:
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
sort_func(random_array)
|
sort_func(z, ctx)
|
||||||
MemoryManager.save_stats(size)
|
ctx.save_stats(size)
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares", "writes"])
|
ctx.plot_stats(["comparisons", "writes"])
|
||||||
|
|
||||||
def swap(z: MemoryArray, i: int, j: int):
|
|
||||||
tmp = z[Literal(i)].value
|
|
||||||
z[Literal(i)] = z[Literal(j)]
|
|
||||||
z[Literal(j)].set(tmp)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
analyze_complexity(insert_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
analyze_complexity(insert_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
||||||
#analyze_complexity(insert_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], True)
|
# analyze_complexity(insert_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], True)
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
from utils.algo_game import Game
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_array import Array
|
||||||
from select_sorting import select_sort_stepwise
|
from select_sorting import select_sort_stepwise
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
class SelectGame(Game):
|
class SelectGame(Game):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Select Game", fps=60, size=(400, 400))
|
super().__init__("Select Game", fps=60, size=(400, 400))
|
||||||
random.seed()
|
random.seed()
|
||||||
l =list(range(1, 101))
|
l = list(range(1, 101))
|
||||||
random.shuffle(l)
|
random.shuffle(l)
|
||||||
self.z = MemoryArray(l)
|
self.ctx = AlgoContext()
|
||||||
|
self.z = Array(l, self.ctx)
|
||||||
self.finished = False
|
self.finished = False
|
||||||
self.sort_generator = select_sort_stepwise(self.z)
|
self.sort_generator = select_sort_stepwise(self.z, self.ctx)
|
||||||
|
|
||||||
def update_game(self):
|
def update_game(self):
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
@@ -29,7 +32,7 @@ class SelectGame(Game):
|
|||||||
def draw_game(self):
|
def draw_game(self):
|
||||||
self.screen.fill(WHITE)
|
self.screen.fill(WHITE)
|
||||||
for i, cell in enumerate(self.z):
|
for i, cell in enumerate(self.z):
|
||||||
x = 50 + i*3
|
x = 50 + i * 3
|
||||||
y = 350 - cell.value * 3
|
y = 350 - cell.value * 3
|
||||||
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
||||||
super().draw_game()
|
super().draw_game()
|
||||||
@@ -38,4 +41,3 @@ class SelectGame(Game):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
b = SelectGame()
|
b = SelectGame()
|
||||||
b.run()
|
b.run()
|
||||||
|
|
||||||
@@ -1,57 +1,46 @@
|
|||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_int import Int
|
||||||
from utils.memory_range import mrange
|
from utils.algo_range import Range
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
def select_sort_stepwise(z: MemoryArray):
|
|
||||||
n = z.length()
|
def select_sort_stepwise(z: Array, ctx: AlgoContext):
|
||||||
cur_min = MemoryCell()
|
"""
|
||||||
for i in mrange(n):
|
Selection Sort – schrittweise Variante (Generator).
|
||||||
cur_min.set(i)
|
|
||||||
for j in mrange(i.succ(), n):
|
Gibt nach jedem Platztausch den aktuellen Array-Zustand zurück.
|
||||||
|
"""
|
||||||
|
n = len(z)
|
||||||
|
cur_min = Int(0, ctx) # Index des aktuellen Minimums
|
||||||
|
|
||||||
|
for i in Range(n):
|
||||||
|
cur_min.set(Int(int(i), ctx))
|
||||||
|
for j in Range(int(i) + 1, n):
|
||||||
if z[j] < z[cur_min]:
|
if z[j] < z[cur_min]:
|
||||||
cur_min.set(j)
|
cur_min.set(Int(int(j), ctx))
|
||||||
swap(z, i, int(cur_min))
|
z.swap(int(i), int(cur_min))
|
||||||
yield z
|
yield z
|
||||||
|
|
||||||
|
|
||||||
def select_sort(z: MemoryArray):
|
def select_sort(z: Array, ctx: AlgoContext):
|
||||||
sort_generator = select_sort_stepwise(z)
|
"""Selection Sort – vollständige Ausführung ohne Visualisierung."""
|
||||||
while True:
|
for _ in select_sort_stepwise(z, ctx):
|
||||||
try:
|
pass
|
||||||
next(sort_generator)
|
|
||||||
except StopIteration:
|
|
||||||
break
|
|
||||||
|
|
||||||
def sort_file(filename, sort_func):
|
|
||||||
z = MemoryArray.create_array_from_file(filename)
|
|
||||||
sort_func(z)
|
|
||||||
return z
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sort_func, sizes, presorted=False):
|
def analyze_complexity(sort_func, sizes, presorted=False):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität einer Sortierfunktion.
|
|
||||||
|
|
||||||
:param sort_func: Die Funktion, die analysiert wird.
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
if presorted:
|
if presorted:
|
||||||
random_array = MemoryArray.create_sorted_array(size)
|
z = Array.sorted(size, ctx)
|
||||||
else:
|
else:
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
sort_func(random_array)
|
sort_func(z, ctx)
|
||||||
MemoryManager.save_stats(size)
|
ctx.save_stats(size)
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares", "writes"])
|
ctx.plot_stats(["comparisons", "writes"])
|
||||||
|
|
||||||
def swap(z: MemoryArray, i: int, j: int):
|
|
||||||
tmp = z[Literal(i)].value
|
|
||||||
z[Literal(i)] = z[Literal(j)]
|
|
||||||
z[Literal(j)].set(tmp)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
analyze_complexity(select_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
analyze_complexity(select_sort, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
from utils.algo_game import Game
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.literal import Literal
|
from utils.algo_array import Array
|
||||||
from heap_sorting import heap_sort_stepwise
|
from heap_sorting import heap_sort_stepwise
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
class HeapGame(Game):
|
class HeapGame(Game):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Heap Game", fps=20, size=(400, 400))
|
super().__init__("Heap Game", fps=20, size=(400, 400))
|
||||||
random.seed()
|
random.seed()
|
||||||
l =list(range(1, 101))
|
l = list(range(1, 101))
|
||||||
random.shuffle(l)
|
random.shuffle(l)
|
||||||
self.z = MemoryArray(l)
|
self.ctx = AlgoContext()
|
||||||
|
self.z = Array(l, self.ctx)
|
||||||
self.finished = False
|
self.finished = False
|
||||||
self.sort_generator = heap_sort_stepwise(self.z)
|
self.sort_generator = heap_sort_stepwise(self.z, self.ctx)
|
||||||
|
|
||||||
def update_game(self):
|
def update_game(self):
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
@@ -30,7 +32,7 @@ class HeapGame(Game):
|
|||||||
def draw_game(self):
|
def draw_game(self):
|
||||||
self.screen.fill(WHITE)
|
self.screen.fill(WHITE)
|
||||||
for i, cell in enumerate(self.z):
|
for i, cell in enumerate(self.z):
|
||||||
x = 50 + i*3
|
x = 50 + i * 3
|
||||||
y = 350 - cell.value * 3
|
y = 350 - cell.value * 3
|
||||||
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
||||||
super().draw_game()
|
super().draw_game()
|
||||||
@@ -39,4 +41,3 @@ class HeapGame(Game):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sort_game = HeapGame()
|
sort_game = HeapGame()
|
||||||
sort_game.run()
|
sort_game.run()
|
||||||
|
|
||||||
@@ -1,93 +1,85 @@
|
|||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
|
||||||
from utils.memory_range import mrange
|
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
def heap_sort_stepwise(z: MemoryArray):
|
|
||||||
n = z.length()
|
# Heap verwendet 1-basierte Indizierung intern;
|
||||||
yield from make_max_heap(z)
|
# adjust_index() rechnet auf 0-basierte Array-Positionen um.
|
||||||
with MemoryCell(n) as heapsize:
|
|
||||||
for i in mrange(n, 1, -1):
|
def left_child(i: int) -> int:
|
||||||
swap(z, 0, i.pred())
|
return 2 * i
|
||||||
|
|
||||||
|
def right_child(i: int) -> int:
|
||||||
|
return 2 * i + 1
|
||||||
|
|
||||||
|
def adjust_index(i: int) -> int:
|
||||||
|
"""Konvertiert 1-basierten Heap-Index in 0-basierten Array-Index."""
|
||||||
|
return i - 1
|
||||||
|
|
||||||
|
|
||||||
|
def heap_sort_stepwise(z: Array, ctx: AlgoContext):
|
||||||
|
"""
|
||||||
|
Heapsort – schrittweise Variante (Generator).
|
||||||
|
|
||||||
|
Baut zunächst einen Max-Heap auf, dann sortiert er durch Tauschen.
|
||||||
|
"""
|
||||||
|
n = len(z)
|
||||||
|
yield from make_max_heap(z, n, ctx)
|
||||||
|
heapsize = n
|
||||||
|
for i in range(n, 1, -1):
|
||||||
|
z.swap(0, i - 1)
|
||||||
yield z
|
yield z
|
||||||
heapsize.set(heapsize.pred())
|
heapsize -= 1
|
||||||
yield from max_heapyfy(z, Literal(1), heapsize)
|
yield from max_heapify(z, 1, heapsize, ctx)
|
||||||
|
|
||||||
|
|
||||||
def heap_sort(z: MemoryArray):
|
def heap_sort(z: Array, ctx: AlgoContext):
|
||||||
sort_generator = heap_sort_stepwise(z)
|
"""Heapsort – vollständige Ausführung ohne Visualisierung."""
|
||||||
while True:
|
for _ in heap_sort_stepwise(z, ctx):
|
||||||
try:
|
pass
|
||||||
next(sort_generator)
|
|
||||||
except StopIteration:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def left_child(i: Literal):
|
def make_max_heap(z: Array, n: int, ctx: AlgoContext):
|
||||||
return Literal(2 * int(i))
|
"""Baut einen Max-Heap in-place auf."""
|
||||||
|
for i in range(n // 2, 0, -1):
|
||||||
|
yield from max_heapify(z, i, n, ctx)
|
||||||
|
|
||||||
|
|
||||||
def right_child(i: Literal):
|
def max_heapify(z: Array, i: int, heapsize: int, ctx: AlgoContext):
|
||||||
return Literal(2 * int(i) + 1)
|
"""
|
||||||
|
Stellt die Max-Heap-Eigenschaft für den Teilbaum bei Index i wieder her.
|
||||||
|
|
||||||
|
i und heapsize sind plain int (1-basiert). Vergleiche auf Array-Inhalten
|
||||||
def adjust_index(i: Literal):
|
werden über Int automatisch gezählt.
|
||||||
return i.pred()
|
"""
|
||||||
|
|
||||||
|
|
||||||
def make_max_heap(z: MemoryArray):
|
|
||||||
n = z.length()
|
|
||||||
for i in mrange(int(n) // 2, 0, -1):
|
|
||||||
yield from max_heapyfy(z, i, n)
|
|
||||||
|
|
||||||
|
|
||||||
def max_heapyfy(z: MemoryArray, i: Literal, heapsize: Literal):
|
|
||||||
l = left_child(i)
|
l = left_child(i)
|
||||||
r = right_child(i)
|
r = right_child(i)
|
||||||
with MemoryCell(i) as max_value:
|
max_val = i
|
||||||
|
|
||||||
if l <= heapsize and z[adjust_index(l)] > z[adjust_index(i)]:
|
if l <= heapsize and z[adjust_index(l)] > z[adjust_index(i)]:
|
||||||
max_value.set(l)
|
max_val = l
|
||||||
if r <= heapsize and z[adjust_index(r)] > z[adjust_index(max_value)]:
|
if r <= heapsize and z[adjust_index(r)] > z[adjust_index(max_val)]:
|
||||||
max_value.set(r)
|
max_val = r
|
||||||
if max_value != i:
|
|
||||||
swap(z, int(i)-1, int(max_value)-1)
|
if max_val != i:
|
||||||
|
z.swap(adjust_index(i), adjust_index(max_val))
|
||||||
yield z
|
yield z
|
||||||
yield from max_heapyfy(z, max_value, heapsize)
|
yield from max_heapify(z, max_val, heapsize, ctx)
|
||||||
|
|
||||||
|
|
||||||
def sort_file(filename, sort_func):
|
|
||||||
z = MemoryArray.create_array_from_file(filename)
|
|
||||||
sort_func(z)
|
|
||||||
return z
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sort_func, sizes, presorted=False):
|
def analyze_complexity(sort_func, sizes, presorted=False):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität einer Sortierfunktion.
|
|
||||||
|
|
||||||
:param sort_func: Die Funktion, die analysiert wird.
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
if presorted:
|
if presorted:
|
||||||
random_array = MemoryArray.create_sorted_array(size)
|
z = Array.sorted(size, ctx)
|
||||||
else:
|
else:
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
sort_func(random_array)
|
sort_func(z, ctx)
|
||||||
MemoryManager.save_stats(size)
|
ctx.save_stats(size)
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares", "writes"])
|
ctx.plot_stats(["comparisons", "writes"])
|
||||||
|
|
||||||
|
|
||||||
def swap(z: MemoryArray, i: int, j: int):
|
|
||||||
tmp = z[Literal(i)].value
|
|
||||||
z[Literal(i)] = z[Literal(j)]
|
|
||||||
z[Literal(j)].set(tmp)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sizes = range(10, 101, 10)
|
sizes = range(10, 101, 10)
|
||||||
analyze_complexity(heap_sort, sizes)
|
analyze_complexity(heap_sort, sizes)
|
||||||
# analyze_complexity(quick_sort, sizes, True)
|
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
from utils.algo_game import Game
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.literal import Literal
|
from utils.algo_array import Array
|
||||||
from quick_sorting import quick_sort_stepwise
|
from quick_sorting import quick_sort_stepwise
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
class QuickGame(Game):
|
class QuickGame(Game):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Quick Game", fps=10, size=(400, 400))
|
super().__init__("Quick Game", fps=10, size=(400, 400))
|
||||||
random.seed()
|
random.seed()
|
||||||
l =list(range(1, 101))
|
l = list(range(1, 101))
|
||||||
random.shuffle(l)
|
random.shuffle(l)
|
||||||
self.z = MemoryArray(l)
|
self.ctx = AlgoContext()
|
||||||
|
self.z = Array(l, self.ctx)
|
||||||
self.finished = False
|
self.finished = False
|
||||||
self.sort_generator = quick_sort_stepwise(self.z, Literal(0), Literal(self.z.length().pred()))
|
self.sort_generator = quick_sort_stepwise(self.z, self.ctx)
|
||||||
|
|
||||||
def update_game(self):
|
def update_game(self):
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
@@ -30,7 +32,7 @@ class QuickGame(Game):
|
|||||||
def draw_game(self):
|
def draw_game(self):
|
||||||
self.screen.fill(WHITE)
|
self.screen.fill(WHITE)
|
||||||
for i, cell in enumerate(self.z):
|
for i, cell in enumerate(self.z):
|
||||||
x = 50 + i*3
|
x = 50 + i * 3
|
||||||
y = 350 - cell.value * 3
|
y = 350 - cell.value * 3
|
||||||
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
pygame.draw.rect(self.screen, BLUE, (x, y, 3, 3))
|
||||||
super().draw_game()
|
super().draw_game()
|
||||||
@@ -39,4 +41,3 @@ class QuickGame(Game):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sort_game = QuickGame()
|
sort_game = QuickGame()
|
||||||
sort_game.run()
|
sort_game.run()
|
||||||
|
|
||||||
@@ -1,81 +1,79 @@
|
|||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_int import Int
|
||||||
from utils.memory_range import mrange
|
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
def quick_sort_stepwise(z: MemoryArray, l: Literal, r: Literal):
|
|
||||||
if l < r:
|
|
||||||
q = partition(z, l, r)
|
|
||||||
yield z
|
|
||||||
yield from quick_sort_stepwise(z, l, q.pred())
|
|
||||||
yield from quick_sort_stepwise(z, q.succ(), r)
|
|
||||||
yield z
|
|
||||||
|
|
||||||
|
|
||||||
def partition(z: MemoryArray, l: Literal, r: Literal):
|
def quick_sort_stepwise(z: Array, ctx: AlgoContext, l: int = 0, r: int = None):
|
||||||
with MemoryCell(z[r]) as pivot, MemoryCell(l) as i, MemoryCell(r.pred()) as j:
|
"""
|
||||||
while i < j:
|
Quicksort – schrittweise Variante (Generator).
|
||||||
while z[i] < pivot:
|
|
||||||
i.set(i.succ())
|
|
||||||
while j > l and z[j] >= pivot:
|
|
||||||
j.set(j.pred())
|
|
||||||
if i < j:
|
|
||||||
swap(z, int(i), int(j))
|
|
||||||
i.set(i.succ())
|
|
||||||
j.set(j.pred())
|
|
||||||
if i == j and z[i] < pivot:
|
|
||||||
i.set(i.succ())
|
|
||||||
if z[i] != pivot:
|
|
||||||
swap(z, int(i), int(r))
|
|
||||||
return Literal(i)
|
|
||||||
|
|
||||||
|
l, r sind 0-basierte Grenzen (plain int). Alle Vergleiche auf Array-Inhalten
|
||||||
def quick_sort(z: MemoryArray, l: Literal = None, r: Literal = None):
|
werden über Int automatisch gezählt.
|
||||||
if l is None:
|
"""
|
||||||
l = Literal(0)
|
|
||||||
if r is None:
|
if r is None:
|
||||||
r = z.length().pred()
|
r = len(z) - 1
|
||||||
sort_generator = quick_sort_stepwise(z, l, r)
|
if l < r:
|
||||||
while True:
|
q = partition(z, l, r, ctx)
|
||||||
try:
|
yield z
|
||||||
next(sort_generator)
|
yield from quick_sort_stepwise(z, ctx, l, q - 1)
|
||||||
except StopIteration:
|
yield from quick_sort_stepwise(z, ctx, q + 1, r)
|
||||||
break
|
yield z
|
||||||
|
|
||||||
|
|
||||||
def sort_file(filename, sort_func):
|
def partition(z: Array, l: int, r: int, ctx: AlgoContext) -> int:
|
||||||
z = MemoryArray.create_array_from_file(filename)
|
"""
|
||||||
sort_func(z)
|
Lomuto-Partitionierung.
|
||||||
return z
|
|
||||||
|
Wählt z[r] als Pivot. Gibt den endgültigen Pivot-Index zurück.
|
||||||
|
"""
|
||||||
|
pivot = Int(z[r].value, ctx) # Pivot-Wert kopieren
|
||||||
|
ctx.reads += 1
|
||||||
|
i = Int(l, ctx)
|
||||||
|
j = Int(r - 1, ctx)
|
||||||
|
|
||||||
|
while i < j:
|
||||||
|
while int(i) <= int(j) and z[i] < pivot:
|
||||||
|
i += 1
|
||||||
|
while int(j) >= l and z[j] >= pivot:
|
||||||
|
j -= 1
|
||||||
|
if i < j:
|
||||||
|
z.swap(int(i), int(j))
|
||||||
|
i += 1
|
||||||
|
j -= 1
|
||||||
|
|
||||||
|
if i == j and z[i] < pivot:
|
||||||
|
i += 1
|
||||||
|
if z[i] != pivot:
|
||||||
|
z.swap(int(i), r)
|
||||||
|
|
||||||
|
return int(i)
|
||||||
|
|
||||||
|
|
||||||
|
def quick_sort(z: Array, ctx: AlgoContext, l: int = None, r: int = None):
|
||||||
|
"""Quicksort – vollständige Ausführung ohne Visualisierung."""
|
||||||
|
if l is None:
|
||||||
|
l = 0
|
||||||
|
if r is None:
|
||||||
|
r = len(z) - 1
|
||||||
|
for _ in quick_sort_stepwise(z, ctx, l, r):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sort_func, sizes, presorted=False):
|
def analyze_complexity(sort_func, sizes, presorted=False):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität einer Sortierfunktion.
|
|
||||||
|
|
||||||
:param sort_func: Die Funktion, die analysiert wird.
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
if presorted:
|
if presorted:
|
||||||
random_array = MemoryArray.create_sorted_array(size)
|
z = Array.sorted(size, ctx)
|
||||||
else:
|
else:
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
sort_func(random_array)
|
sort_func(z, ctx)
|
||||||
MemoryManager.save_stats(size)
|
ctx.save_stats(size)
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares", "writes"])
|
ctx.plot_stats(["comparisons", "writes"])
|
||||||
|
|
||||||
|
|
||||||
def swap(z: MemoryArray, i: int, j: int):
|
|
||||||
tmp = z[Literal(i)].value
|
|
||||||
z[Literal(i)] = z[Literal(j)]
|
|
||||||
z[Literal(j)].set(tmp)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sizes = range(10, 101, 5)
|
sizes = range(10, 101, 5)
|
||||||
#analyze_complexity(quick_sort, sizes)
|
analyze_complexity(quick_sort, sizes)
|
||||||
analyze_complexity(quick_sort, sizes, True)
|
# analyze_complexity(quick_sort, sizes, True)
|
||||||
@@ -1,60 +1,53 @@
|
|||||||
from utils.memory_array import MemoryArray
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_range import Range
|
||||||
from utils.memory_range import mrange
|
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
|
|
||||||
|
def count_sort(a: Array, b: Array, k: int, ctx: AlgoContext):
|
||||||
|
"""
|
||||||
|
Counting Sort.
|
||||||
|
|
||||||
def count_sort(a: MemoryArray, b: MemoryArray, k: int):
|
a – Eingabe-Array mit Werten aus [0, k]
|
||||||
c = MemoryArray(Literal(k + 1))
|
b – Ausgabe-Array (gleiche Länge wie a)
|
||||||
for i in mrange(Literal(k + 1)):
|
k – maximaler Wert in a
|
||||||
c[i].set(Literal(0))
|
"""
|
||||||
|
c = Array([0] * (k + 1), ctx) # Zählarray
|
||||||
|
|
||||||
for j in mrange(a.length()):
|
# Häufigkeiten zählen
|
||||||
c[a[j]].set(c[a[j]].succ())
|
for j in Range(len(a)):
|
||||||
|
c[a[j]] = c[a[j]] + 1
|
||||||
|
|
||||||
for i in mrange(Literal(1), Literal(k + 1)):
|
# Kumulierte Summen bilden
|
||||||
c[i].set(int(c[i]) + int(c[i.pred()]))
|
for i in Range(1, k + 1):
|
||||||
|
c[i] = c[i] + c[i - 1]
|
||||||
for j in mrange(a.length().pred(), Literal(-1), Literal(-1)):
|
|
||||||
b[c[a[j]].pred()].set(a[j])
|
|
||||||
c[a[j]].set(c[a[j]].pred())
|
|
||||||
|
|
||||||
|
# Stabil in b einsortieren (rückwärts für Stabilität)
|
||||||
|
for j in Range(len(a) - 1, -1, -1):
|
||||||
|
b[c[a[j]] - 1] = a[j]
|
||||||
|
c[a[j]] = c[a[j]] - 1
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sizes, presorted=False):
|
def analyze_complexity(sizes, presorted=False):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität einer Sortierfunktion.
|
|
||||||
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
if presorted:
|
if presorted:
|
||||||
random_array = MemoryArray.create_sorted_array(size, 0, 100)
|
z = Array.sorted(size, ctx)
|
||||||
else:
|
else:
|
||||||
random_array = MemoryArray.create_random_array(size, 0, 100)
|
z = Array.random(size, 0, 100, ctx)
|
||||||
dest_array = MemoryArray(Literal(size))
|
dest = Array([0] * size, ctx)
|
||||||
count_sort(random_array, dest_array, 100)
|
count_sort(z, dest, 100, ctx)
|
||||||
MemoryManager.save_stats(size)
|
ctx.save_stats(size)
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares", "writes"])
|
ctx.plot_stats(["reads", "writes"])
|
||||||
|
|
||||||
|
|
||||||
def swap(z: MemoryArray, i: int, j: int):
|
|
||||||
tmp = z[Literal(i)].value
|
|
||||||
z[Literal(i)] = z[Literal(j)]
|
|
||||||
z[Literal(j)].set(tmp)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
ctx = AlgoContext()
|
||||||
# Test the count_sort function
|
a = Array([2, 5, 3, 0, 2, 3, 0, 3], ctx)
|
||||||
a = MemoryArray([2, 5, 3, 0, 2, 3, 0, 3])
|
b = Array([0] * len(a), ctx)
|
||||||
b = MemoryArray(Literal(len(a)))
|
count_sort(a, b, 5, ctx)
|
||||||
count_sort(a, b, 5)
|
print(b)
|
||||||
|
|
||||||
sizes = range(10, 101, 10)
|
sizes = range(10, 101, 10)
|
||||||
analyze_complexity(sizes)
|
analyze_complexity(sizes)
|
||||||
# analyze_complexity(sizes, True)
|
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_array import Array
|
||||||
from utils.literal import Literal
|
|
||||||
from vorlesung.L05_binaere_baeume.avl_tree import AVLTree
|
from vorlesung.L05_binaere_baeume.avl_tree import AVLTree
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sizes):
|
def analyze_complexity(sizes):
|
||||||
"""
|
import random
|
||||||
Analysiert die Komplexität
|
sizes = list(sizes)
|
||||||
|
elements = random.sample(range(max(sizes) * 10), max(sizes))
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
ctx = AlgoContext()
|
||||||
"""
|
tree = AVLTree(ctx)
|
||||||
|
prev = 0
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
for i in range(prev, size - 1):
|
||||||
tree = AVLTree()
|
tree.insert(elements[i])
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
ctx.reset()
|
||||||
for i in range(size-1):
|
tree.insert(elements[size - 1])
|
||||||
tree.insert(int(random_array[Literal(i)]))
|
ctx.save_stats(size)
|
||||||
MemoryManager.reset()
|
prev = size
|
||||||
tree.insert(int(random_array[Literal(size-1)]))
|
|
||||||
MemoryManager.save_stats(size)
|
ctx.plot_stats(["comparisons"])
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares"])
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sizes = range(1, 1001, 2)
|
sizes = range(10, 501, 10)
|
||||||
analyze_complexity(sizes)
|
analyze_complexity(sizes)
|
||||||
@@ -1,26 +1,28 @@
|
|||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_array import Array
|
||||||
from utils.literal import Literal
|
|
||||||
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
|
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sizes):
|
def analyze_complexity(sizes):
|
||||||
"""
|
import random
|
||||||
Analysiert die Komplexität
|
sizes = list(sizes)
|
||||||
|
# Alle Elemente vorab erzeugen – Baum wird inkrementell aufgebaut statt
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
# für jede Größe neu, dadurch O(n log n) statt O(n²) Gesamtaufwand.
|
||||||
"""
|
elements = random.sample(range(max(sizes) * 10), max(sizes))
|
||||||
|
ctx = AlgoContext()
|
||||||
|
tree = BinaryTree(ctx)
|
||||||
|
prev = 0
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
for i in range(prev, size - 1): # Warm-up: ohne Messung einfügen
|
||||||
tree = BinaryTree()
|
tree.insert(elements[i])
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
ctx.reset()
|
||||||
for i in range(size-1):
|
tree.insert(elements[size - 1]) # gemessener Insert
|
||||||
tree.insert(int(random_array[Literal(i)]))
|
ctx.save_stats(size)
|
||||||
MemoryManager.reset()
|
prev = size
|
||||||
tree.insert(int(random_array[Literal(size-1)]))
|
|
||||||
MemoryManager.save_stats(size)
|
ctx.plot_stats(["comparisons"])
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares"])
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sizes = range(1, 1001, 2)
|
sizes = range(10, 501, 10)
|
||||||
analyze_complexity(sizes)
|
analyze_complexity(sizes)
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
from utils.memory_array import MemoryArray
|
|
||||||
from vorlesung.L05_binaere_baeume.avl_tree_node import AVLTreeNode
|
from vorlesung.L05_binaere_baeume.avl_tree_node import AVLTreeNode
|
||||||
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
|
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
|
||||||
import logging
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_array import Array
|
||||||
|
|
||||||
|
|
||||||
class AVLTree(BinaryTree):
|
class AVLTree(BinaryTree):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, ctx: AlgoContext):
|
||||||
super().__init__()
|
super().__init__(ctx)
|
||||||
|
|
||||||
def new_node(self, value):
|
def new_node(self, value):
|
||||||
return AVLTreeNode(value)
|
return AVLTreeNode(value, self.ctx)
|
||||||
|
|
||||||
|
|
||||||
def balance(self, node: AVLTreeNode):
|
def balance(self, node: AVLTreeNode):
|
||||||
node.update_balance()
|
node.update_balance()
|
||||||
@@ -47,7 +47,6 @@ class AVLTree(BinaryTree):
|
|||||||
self.balance(parent)
|
self.balance(parent)
|
||||||
return node, parent
|
return node, parent
|
||||||
|
|
||||||
|
|
||||||
def delete(self, value):
|
def delete(self, value):
|
||||||
node, parent = super().delete(value)
|
node, parent = super().delete(value)
|
||||||
if node:
|
if node:
|
||||||
@@ -58,37 +57,25 @@ class AVLTree(BinaryTree):
|
|||||||
def graph_filename(self):
|
def graph_filename(self):
|
||||||
return "AVLTree"
|
return "AVLTree"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
ctx = AlgoContext()
|
||||||
|
tree = AVLTree(ctx)
|
||||||
|
|
||||||
|
values = Array.from_file("data/seq2.txt", ctx)
|
||||||
|
for cell in values:
|
||||||
|
tree.insert(cell.value)
|
||||||
|
|
||||||
def print_node(node, indent=0, level=0):
|
def print_node(node, indent=0, level=0):
|
||||||
print((indent * 3) * " ", node.value)
|
print((indent * 3) * " ", node.value)
|
||||||
|
|
||||||
tree = AVLTree()
|
|
||||||
#values = [5, 3, 7, 2, 4, 6, 5, 8]
|
|
||||||
values = MemoryArray.create_array_from_file("data/seq2.txt")
|
|
||||||
|
|
||||||
for value in values:
|
|
||||||
tree.insert(value)
|
|
||||||
|
|
||||||
|
|
||||||
print("In-order traversal:")
|
print("In-order traversal:")
|
||||||
tree.in_order_traversal(print_node)
|
tree.in_order_traversal(print_node)
|
||||||
print("\nLevel-order traversal:")
|
|
||||||
tree.level_order_traversal(print_node)
|
|
||||||
print("\nTree structure traversal:")
|
print("\nTree structure traversal:")
|
||||||
tree.tree_structure_traversal(print_node)
|
tree.tree_structure_traversal(print_node)
|
||||||
print("\nGraph traversal:")
|
|
||||||
tree.graph_traversal()
|
|
||||||
|
|
||||||
tree.insert(9)
|
tree.insert(9)
|
||||||
tree.graph_traversal()
|
|
||||||
|
|
||||||
print("\nDeleting 5:")
|
print("\nDeleting 5:")
|
||||||
tree.delete(5)
|
tree.delete(5)
|
||||||
|
|
||||||
print("In-order traversal after deletion:")
|
print("In-order traversal after deletion:")
|
||||||
tree.in_order_traversal(print_node)
|
tree.in_order_traversal(print_node)
|
||||||
print("\nLevel-order traversal after deletion:")
|
|
||||||
tree.level_order_traversal(print_node)
|
|
||||||
print("\nTree structure traversal after deletion:")
|
|
||||||
tree.tree_structure_traversal(print_node)
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
from utils.algo_game import Game
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
from avl_tree import AVLTree
|
from avl_tree import AVLTree
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
BLACK = (0, 0, 0)
|
BLACK = (0, 0, 0)
|
||||||
WIDTH = 800
|
WIDTH, HEIGHT, MARGIN = 800, 400, 20
|
||||||
HEIGHT = 400
|
|
||||||
MARGIN = 20
|
|
||||||
|
|
||||||
class AVLTreeGame(Game):
|
class AVLTreeGame(Game):
|
||||||
|
|
||||||
@@ -18,8 +18,12 @@ class AVLTreeGame(Game):
|
|||||||
self.z = list(range(1, 501))
|
self.z = list(range(1, 501))
|
||||||
random.shuffle(self.z)
|
random.shuffle(self.z)
|
||||||
self.finished = False
|
self.finished = False
|
||||||
self.tree = AVLTree()
|
self.ctx = AlgoContext()
|
||||||
self.tree.get_height = lambda node: 0 if node is None else 1 + max(self.tree.get_height(node.left), self.tree.get_height(node.right))
|
self.tree = AVLTree(self.ctx)
|
||||||
|
self.tree.get_height = lambda node: (
|
||||||
|
0 if node is None
|
||||||
|
else 1 + max(self.tree.get_height(node.left), self.tree.get_height(node.right))
|
||||||
|
)
|
||||||
self.height = self.tree.get_height(self.tree.root)
|
self.height = self.tree.get_height(self.tree.root)
|
||||||
self.generator = None
|
self.generator = None
|
||||||
|
|
||||||
@@ -43,7 +47,7 @@ class AVLTreeGame(Game):
|
|||||||
super().draw_game()
|
super().draw_game()
|
||||||
|
|
||||||
def draw_tree(self, node, x, y, x_offset):
|
def draw_tree(self, node, x, y, x_offset):
|
||||||
y_offset = (HEIGHT - (2 * MARGIN)) / self.height
|
y_offset = (HEIGHT - 2 * MARGIN) / self.height
|
||||||
if node is not None:
|
if node is not None:
|
||||||
pygame.draw.circle(self.screen, BLUE, (x, y), 2)
|
pygame.draw.circle(self.screen, BLUE, (x, y), 2)
|
||||||
if node.left is not None:
|
if node.left is not None:
|
||||||
@@ -53,7 +57,7 @@ class AVLTreeGame(Game):
|
|||||||
pygame.draw.line(self.screen, BLACK, (x, y), (x + x_offset, y + y_offset))
|
pygame.draw.line(self.screen, BLACK, (x, y), (x + x_offset, y + y_offset))
|
||||||
self.draw_tree(node.right, x + x_offset, y + y_offset, x_offset // 2)
|
self.draw_tree(node.right, x + x_offset, y + y_offset, x_offset // 2)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
tree_game = AVLTreeGame()
|
tree_game = AVLTreeGame()
|
||||||
tree_game.run()
|
tree_game.run()
|
||||||
|
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode
|
from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
|
||||||
|
|
||||||
class AVLTreeNode(BinaryTreeNode):
|
class AVLTreeNode(BinaryTreeNode):
|
||||||
def __init__(self, value):
|
|
||||||
super().__init__(value)
|
def __init__(self, value, ctx: AlgoContext):
|
||||||
|
super().__init__(value, ctx)
|
||||||
self.parent = None
|
self.parent = None
|
||||||
self.balance = 0
|
self.balance = 0 # plain int – Metadaten, kein Zähler
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"TreeNode(id={id(self)} value={self.value}, left={self.left}, right={self.right})"
|
return f"TreeNode(id={id(self)} value={self.value}, left={self.left}, right={self.right})"
|
||||||
@@ -58,4 +61,3 @@ class AVLTreeNode(BinaryTreeNode):
|
|||||||
def left_right_rotate(self):
|
def left_right_rotate(self):
|
||||||
self.left = self.left.left_rotate()
|
self.left = self.left.left_rotate()
|
||||||
return self.right_rotate()
|
return self.right_rotate()
|
||||||
|
|
||||||
@@ -1,61 +1,53 @@
|
|||||||
import random
|
import random
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_array import Array
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_int import Int
|
||||||
from utils.memory_manager import MemoryManager
|
|
||||||
from utils.memory_range import mrange
|
|
||||||
from utils.literal import Literal
|
|
||||||
|
|
||||||
|
|
||||||
def binary_search(z: MemoryArray, s: MemoryCell, l: Literal = None, r: Literal = None):
|
def binary_search(z: Array, s: Int, l: int = None, r: int = None):
|
||||||
"""
|
"""
|
||||||
Perform a binary search on the sorted array z for the value x.
|
Binäre Suche auf dem sortierten Array z nach dem Wert s.
|
||||||
|
|
||||||
|
l, r – 0-basierte Grenzen (plain int, optional).
|
||||||
|
Gibt den Index als plain int zurück, oder None wenn nicht gefunden.
|
||||||
"""
|
"""
|
||||||
if l is None:
|
if l is None:
|
||||||
l = Literal(0)
|
l = 0
|
||||||
if r is None:
|
if r is None:
|
||||||
r = Literal(z.length().pred())
|
r = len(z) - 1
|
||||||
if l > r:
|
if l > r:
|
||||||
return None
|
return None
|
||||||
with MemoryCell(l) as m:
|
|
||||||
m += r
|
m = Int((l + r) // 2, s._ctx)
|
||||||
m //= Literal(2)
|
|
||||||
if s < z[m]:
|
if s < z[m]:
|
||||||
return binary_search(z, s, l, m.pred())
|
return binary_search(z, s, l, int(m) - 1)
|
||||||
elif s > z[m]:
|
elif s > z[m]:
|
||||||
return binary_search(z, s, m.succ(), r)
|
return binary_search(z, s, int(m) + 1, r)
|
||||||
else:
|
else:
|
||||||
return m
|
return int(m)
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sizes):
|
def analyze_complexity(sizes):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität
|
|
||||||
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
random_array = MemoryArray.create_sorted_array(size)
|
z = Array.sorted(size, ctx)
|
||||||
search_value = random.randint(-100, 100)
|
search_value = Int(random.randint(0, size - 1), ctx)
|
||||||
binary_search(random_array, MemoryCell(search_value))
|
binary_search(z, search_value)
|
||||||
MemoryManager.save_stats(size)
|
ctx.save_stats(size)
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares", "adds"])
|
|
||||||
|
|
||||||
|
|
||||||
|
ctx.plot_stats(["comparisons", "additions"])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Example usage
|
ctx = AlgoContext()
|
||||||
arr = MemoryArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
arr = Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ctx)
|
||||||
search_value = MemoryCell(8)
|
s = Int(8, ctx)
|
||||||
result = binary_search(arr, search_value)
|
result = binary_search(arr, s)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
print(f"Value {search_value} found at index {result}.")
|
print(f"Value {s} found at index {result}.")
|
||||||
else:
|
else:
|
||||||
print(f"Value {search_value} not found in the array.")
|
print(f"Value {s} not found.")
|
||||||
|
|
||||||
|
|
||||||
sizes = range(1, 1001, 2)
|
sizes = range(1, 1001, 2)
|
||||||
analyze_complexity(sizes)
|
analyze_complexity(sizes)
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode
|
from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode
|
||||||
from utils.project_dir import get_path
|
from utils.algo_context import AlgoContext
|
||||||
|
from utils.algo_path import path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import graphviz
|
import graphviz
|
||||||
|
|
||||||
|
|
||||||
class BinaryTree:
|
class BinaryTree:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, ctx: AlgoContext):
|
||||||
self.root = None
|
self.root = None
|
||||||
self.size = 0
|
self.size = 0
|
||||||
|
self.ctx = ctx
|
||||||
|
|
||||||
def new_node(self, value):
|
def new_node(self, value):
|
||||||
return BinaryTreeNode(value)
|
return BinaryTreeNode(value, self.ctx)
|
||||||
|
|
||||||
def insert(self, value):
|
def insert(self, value):
|
||||||
self.size += 1
|
self.size += 1
|
||||||
@@ -40,7 +42,7 @@ class BinaryTree:
|
|||||||
def search(self, value):
|
def search(self, value):
|
||||||
current = self.root
|
current = self.root
|
||||||
value = self.new_node(value)
|
value = self.new_node(value)
|
||||||
while current:
|
while current is not None:
|
||||||
if value < current:
|
if value < current:
|
||||||
current = current.left
|
current = current.left
|
||||||
elif value > current:
|
elif value > current:
|
||||||
@@ -50,9 +52,6 @@ class BinaryTree:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def delete(self, value):
|
def delete(self, value):
|
||||||
# Der Wert wird im Baum gesucht und der erste Treffer gelöscht
|
|
||||||
# Rückgabe falls der Wert gefunden wird:
|
|
||||||
# der Knoten, der den zu löschenden Knoten ersetzt und der Elternknoten des gelöschten Knotens
|
|
||||||
parent = None
|
parent = None
|
||||||
current = self.root
|
current = self.root
|
||||||
value = self.new_node(value)
|
value = self.new_node(value)
|
||||||
@@ -64,38 +63,25 @@ class BinaryTree:
|
|||||||
parent = current
|
parent = current
|
||||||
current = current.right
|
current = current.right
|
||||||
else:
|
else:
|
||||||
# Knoten gefunden
|
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# Wert nicht gefunden
|
|
||||||
return None, None
|
return None, None
|
||||||
return self.delete_node(current, parent)
|
return self.delete_node(current, parent)
|
||||||
|
|
||||||
def delete_node(self, current, parent):
|
def delete_node(self, current, parent):
|
||||||
# Der übergebene Knoten wird
|
|
||||||
# Rückgabe ist ein Tupel:
|
|
||||||
# der Knoten, der den zu löschenden Knoten ersetzt und der Elternknoten des gelöschten Knotens
|
|
||||||
self.size -= 1
|
self.size -= 1
|
||||||
# Fall 3: Es gibt zwei Kinder: wir suchen den Nachfolger
|
# Fall 3: zwei Kinder → Nachfolger suchen
|
||||||
if current.left and current.right:
|
if current.left and current.right:
|
||||||
parent = current
|
parent = current
|
||||||
successor = current.right
|
successor = current.right
|
||||||
while successor.left:
|
while successor.left:
|
||||||
parent = successor
|
parent = successor
|
||||||
successor = successor.left
|
successor = successor.left
|
||||||
# Wert des Nachfolgers wird in den Knoten geschrieben, der gelöscht werden soll
|
current.set(successor) # Wert kopieren (1 read + 1 write)
|
||||||
current.value = successor.value
|
|
||||||
# Ab jetzt muss successor gelöscht werden; parent ist bereits richtig gesetzt
|
|
||||||
current = successor
|
current = successor
|
||||||
|
|
||||||
# Ermitteln des einen Kindes (falls es eines gibt), sonst None
|
child = current.left if current.left else current.right
|
||||||
# Das eine Kind ist der Ersatz für den Knoten, der gelöscht werden soll
|
|
||||||
if current.left:
|
|
||||||
child = current.left
|
|
||||||
else:
|
|
||||||
child = current.right
|
|
||||||
|
|
||||||
# Falls es keinen Elternknoten gibt, ist der Ersatzknoten die Wurzel
|
|
||||||
if not parent:
|
if not parent:
|
||||||
self.root = child
|
self.root = child
|
||||||
return child, None
|
return child, None
|
||||||
@@ -106,17 +92,13 @@ class BinaryTree:
|
|||||||
parent.right = child
|
parent.right = child
|
||||||
return child, parent
|
return child, parent
|
||||||
|
|
||||||
|
|
||||||
def in_order_traversal(self, callback):
|
def in_order_traversal(self, callback):
|
||||||
|
def _rec(callback, current):
|
||||||
def in_order_traversal_recursive(callback, current):
|
|
||||||
if current is not None:
|
if current is not None:
|
||||||
in_order_traversal_recursive(callback, current.left)
|
_rec(callback, current.left)
|
||||||
callback(current)
|
callback(current)
|
||||||
in_order_traversal_recursive(callback, current.right)
|
_rec(callback, current.right)
|
||||||
|
_rec(callback, self.root)
|
||||||
in_order_traversal_recursive(callback, self.root)
|
|
||||||
|
|
||||||
|
|
||||||
def level_order_traversal(self, callback):
|
def level_order_traversal(self, callback):
|
||||||
if self.root is None:
|
if self.root is None:
|
||||||
@@ -125,23 +107,19 @@ class BinaryTree:
|
|||||||
while queue:
|
while queue:
|
||||||
current, level = queue.pop(0)
|
current, level = queue.pop(0)
|
||||||
callback(current, level)
|
callback(current, level)
|
||||||
if current.left is not None:
|
if current.left is not None: queue.append((current.left, level + 1))
|
||||||
queue.append((current.left, level + 1))
|
if current.right is not None: queue.append((current.right, level + 1))
|
||||||
if current.right is not None:
|
|
||||||
queue.append((current.right, level + 1))
|
|
||||||
|
|
||||||
def tree_structure_traversal(self, callback):
|
def tree_structure_traversal(self, callback):
|
||||||
|
def _rec(callback, current, level):
|
||||||
def tree_structure_traversal_recursive(callback, current, level):
|
|
||||||
nonlocal line
|
nonlocal line
|
||||||
if current:
|
if current:
|
||||||
tree_structure_traversal_recursive(callback, current.left, level + 1)
|
_rec(callback, current.left, level + 1)
|
||||||
callback(current, level, line)
|
callback(current, level, line)
|
||||||
line += 1
|
line += 1
|
||||||
tree_structure_traversal_recursive(callback, current.right, level + 1)
|
_rec(callback, current.right, level + 1)
|
||||||
|
|
||||||
line = 0
|
line = 0
|
||||||
tree_structure_traversal_recursive(callback, self.root, 0)
|
_rec(callback, self.root, 0)
|
||||||
|
|
||||||
def graph_filename(self):
|
def graph_filename(self):
|
||||||
return "BinaryTree"
|
return "BinaryTree"
|
||||||
@@ -152,55 +130,44 @@ class BinaryTree:
|
|||||||
if node is not None:
|
if node is not None:
|
||||||
node.graphviz_rep(level, line, dot)
|
node.graphviz_rep(level, line, dot)
|
||||||
|
|
||||||
def graph_traversal_recursive(current):
|
def _rec(current):
|
||||||
nonlocal dot
|
nonlocal dot
|
||||||
if current is not None:
|
if current is not None:
|
||||||
if current.left:
|
if current.left:
|
||||||
dot.edge(str(id(current)), str(id(current.left)))
|
dot.edge(str(id(current)), str(id(current.left)))
|
||||||
graph_traversal_recursive(current.left)
|
_rec(current.left)
|
||||||
if current.right:
|
if current.right:
|
||||||
dot.edge(str(id(current)), str(id(current.right)))
|
dot.edge(str(id(current)), str(id(current.right)))
|
||||||
graph_traversal_recursive(current.right)
|
_rec(current.right)
|
||||||
|
|
||||||
dot = graphviz.Digraph( name="BinaryTree",
|
dot = graphviz.Digraph(
|
||||||
|
name="BinaryTree",
|
||||||
engine="neato",
|
engine="neato",
|
||||||
node_attr={"shape": "circle", "fontname": "Arial"},
|
node_attr={"shape": "circle", "fontname": "Arial"},
|
||||||
format="pdf" )
|
format="pdf")
|
||||||
self.tree_structure_traversal(define_node)
|
self.tree_structure_traversal(define_node)
|
||||||
graph_traversal_recursive(self.root)
|
_rec(self.root)
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
filename = f"{self.graph_filename()}_{timestamp}.gv"
|
dot.render(path(f"{self.graph_filename()}_{timestamp}.gv"))
|
||||||
filename = get_path(filename)
|
|
||||||
dot.render(filename)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
tree = BinaryTree()
|
ctx = AlgoContext()
|
||||||
values = [5, 3, 7, 2, 4, 6, 5, 8]
|
tree = BinaryTree(ctx)
|
||||||
|
for v in [5, 3, 7, 2, 4, 6, 5, 8]:
|
||||||
for value in values:
|
tree.insert(v)
|
||||||
tree.insert(value)
|
|
||||||
|
|
||||||
def print_node(node, indent=0, line=None):
|
def print_node(node, indent=0, line=None):
|
||||||
print((indent * 3) * " ", node.value)
|
print((indent * 3) * " ", node.value)
|
||||||
|
|
||||||
|
|
||||||
print("In-order traversal:")
|
print("In-order traversal:")
|
||||||
tree.in_order_traversal(print_node)
|
tree.in_order_traversal(print_node)
|
||||||
print("\nLevel-order traversal:")
|
print("\nLevel-order traversal:")
|
||||||
tree.level_order_traversal(print_node)
|
tree.level_order_traversal(print_node)
|
||||||
print("\nTree structure traversal:")
|
|
||||||
tree.tree_structure_traversal(print_node)
|
|
||||||
print("\nGraph traversal:")
|
|
||||||
tree.graph_traversal()
|
|
||||||
|
|
||||||
print("\nDeleting 5:")
|
print("\nDeleting 5:")
|
||||||
tree.delete(5)
|
tree.delete(5)
|
||||||
|
|
||||||
print("In-order traversal after deletion:")
|
print("In-order traversal after deletion:")
|
||||||
tree.in_order_traversal(print_node)
|
tree.in_order_traversal(print_node)
|
||||||
print("\nLevel-order traversal after deletion:")
|
|
||||||
tree.level_order_traversal(print_node)
|
|
||||||
print("\nTree structure traversal after deletion:")
|
|
||||||
tree.tree_structure_traversal(print_node)
|
|
||||||
|
|
||||||
|
|
||||||
|
print("\n", ctx)
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import random
|
import random
|
||||||
import pygame
|
import pygame
|
||||||
from utils.game import Game
|
from utils.algo_game import Game
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
from bin_tree import BinaryTree
|
from bin_tree import BinaryTree
|
||||||
|
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
BLUE = (0, 0, 255)
|
BLUE = (0, 0, 255)
|
||||||
BLACK = (0, 0, 0)
|
BLACK = (0, 0, 0)
|
||||||
WIDTH = 800
|
WIDTH, HEIGHT, MARGIN = 800, 400, 20
|
||||||
HEIGHT = 400
|
|
||||||
MARGIN = 20
|
|
||||||
|
|
||||||
class BinTreeGame(Game):
|
class BinTreeGame(Game):
|
||||||
|
|
||||||
@@ -18,14 +18,17 @@ class BinTreeGame(Game):
|
|||||||
self.z = list(range(1, 101))
|
self.z = list(range(1, 101))
|
||||||
random.shuffle(self.z)
|
random.shuffle(self.z)
|
||||||
self.finished = False
|
self.finished = False
|
||||||
self.tree = BinaryTree()
|
self.ctx = AlgoContext()
|
||||||
self.tree.get_height = lambda node: 0 if node is None else 1 + max(self.tree.get_height(node.left), self.tree.get_height(node.right))
|
self.tree = BinaryTree(self.ctx)
|
||||||
|
self.tree.get_height = lambda node: (
|
||||||
|
0 if node is None
|
||||||
|
else 1 + max(self.tree.get_height(node.left), self.tree.get_height(node.right))
|
||||||
|
)
|
||||||
self.height = self.tree.get_height(self.tree.root)
|
self.height = self.tree.get_height(self.tree.root)
|
||||||
|
|
||||||
def update_game(self):
|
def update_game(self):
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
i = self.z.pop()
|
self.tree.insert(self.z.pop())
|
||||||
self.tree.insert(i)
|
|
||||||
self.height = self.tree.get_height(self.tree.root)
|
self.height = self.tree.get_height(self.tree.root)
|
||||||
if len(self.z) == 0:
|
if len(self.z) == 0:
|
||||||
self.finished = True
|
self.finished = True
|
||||||
@@ -38,7 +41,7 @@ class BinTreeGame(Game):
|
|||||||
super().draw_game()
|
super().draw_game()
|
||||||
|
|
||||||
def draw_tree(self, node, x, y, x_offset):
|
def draw_tree(self, node, x, y, x_offset):
|
||||||
y_offset = (HEIGHT - (2 * MARGIN)) / self.height
|
y_offset = (HEIGHT - 2 * MARGIN) / self.height
|
||||||
if node is not None:
|
if node is not None:
|
||||||
pygame.draw.circle(self.screen, BLUE, (x, y), 2)
|
pygame.draw.circle(self.screen, BLUE, (x, y), 2)
|
||||||
if node.left is not None:
|
if node.left is not None:
|
||||||
@@ -48,7 +51,7 @@ class BinTreeGame(Game):
|
|||||||
pygame.draw.line(self.screen, BLACK, (x, y), (x + x_offset, y + y_offset))
|
pygame.draw.line(self.screen, BLACK, (x, y), (x + x_offset, y + y_offset))
|
||||||
self.draw_tree(node.right, x + x_offset, y + y_offset, x_offset // 2)
|
self.draw_tree(node.right, x + x_offset, y + y_offset, x_offset // 2)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
tree_game = BinTreeGame()
|
tree_game = BinTreeGame()
|
||||||
tree_game.run()
|
tree_game.run()
|
||||||
|
|
||||||
@@ -1,15 +1,23 @@
|
|||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_int import Int
|
||||||
|
from utils.algo_context import AlgoContext
|
||||||
|
|
||||||
class BinaryTreeNode(MemoryCell):
|
|
||||||
|
|
||||||
def __init__(self, value):
|
class BinaryTreeNode(Int):
|
||||||
super().__init__(value)
|
"""
|
||||||
|
Knoten eines binären Suchbaums.
|
||||||
|
|
||||||
|
Erbt von Int – Vergleiche zwischen Knoten werden automatisch im
|
||||||
|
AlgoContext gezählt.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, value, ctx: AlgoContext):
|
||||||
|
super().__init__(value, ctx)
|
||||||
self.left = None
|
self.left = None
|
||||||
self.right = None
|
self.right = None
|
||||||
|
|
||||||
def height(self):
|
def height(self):
|
||||||
left_height = self.left.height() if self.left else 0
|
left_height = self.left.height() if self.left is not None else 0
|
||||||
right_height = self.right.height() if self.right else 0
|
right_height = self.right.height() if self.right is not None else 0
|
||||||
return 1 + max(left_height, right_height)
|
return 1 + max(left_height, right_height)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|||||||
@@ -1,57 +1,62 @@
|
|||||||
from utils.memory_manager import MemoryManager
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_array import Array
|
||||||
from utils.literal import Literal
|
|
||||||
from b_tree import BTree
|
from b_tree import BTree
|
||||||
from b_tree_node import BTreeNode
|
from b_tree_node import BTreeNode
|
||||||
|
|
||||||
class MemoryManagerBTree(MemoryManager):
|
|
||||||
"""
|
|
||||||
Diese Klasse erweitert den MemoryManager, um spezifische Statistiken für B-Bäume zu speichern.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
def count_loads(root: BTreeNode) -> int:
|
||||||
def count_loads():
|
"""Summiert load()-Aufrufe über alle Knoten des Baums."""
|
||||||
return sum([cell.loaded_count for cell in MemoryManager().cells if isinstance(cell, BTreeNode)])
|
if root is None:
|
||||||
|
return 0
|
||||||
@staticmethod
|
total = root.loaded_count
|
||||||
def count_saves():
|
for child in root.children:
|
||||||
return sum([cell.saved_count for cell in MemoryManager().cells if isinstance(cell, BTreeNode)])
|
if child is not None:
|
||||||
|
total += count_loads(child)
|
||||||
@staticmethod
|
return total
|
||||||
def save_stats(count):
|
|
||||||
data = { "cells": MemoryManager.count_cells(),
|
|
||||||
"reads": MemoryManager.count_reads(),
|
|
||||||
"writes": MemoryManager.count_writes(),
|
|
||||||
"compares": MemoryManager.count_compares(),
|
|
||||||
"adds": MemoryManager.count_adds(),
|
|
||||||
"subs": MemoryManager.count_subs(),
|
|
||||||
"muls": MemoryManager.count_muls(),
|
|
||||||
"divs": MemoryManager.count_divs(),
|
|
||||||
"bitops": MemoryManager.count_bitops(),
|
|
||||||
"loads": MemoryManagerBTree.count_loads(),
|
|
||||||
"saves": MemoryManagerBTree.count_saves() }
|
|
||||||
MemoryManager.stats[count] = data
|
|
||||||
|
|
||||||
|
|
||||||
|
def count_saves(root: BTreeNode) -> int:
|
||||||
|
"""Summiert save()-Aufrufe über alle Knoten des Baums."""
|
||||||
|
if root is None:
|
||||||
|
return 0
|
||||||
|
total = root.saved_count
|
||||||
|
for child in root.children:
|
||||||
|
if child is not None:
|
||||||
|
total += count_saves(child)
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sizes):
|
def analyze_complexity(sizes):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität
|
stats: dict[int, dict] = {}
|
||||||
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
tree = BTree(5)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
tree = BTree(5, ctx)
|
||||||
for i in range(size-1):
|
for i in range(size - 1):
|
||||||
tree.insert(int(random_array[Literal(i)]))
|
tree.insert(z[i])
|
||||||
MemoryManager.reset()
|
ctx.reset()
|
||||||
tree.insert(int(random_array[Literal(size-1)]))
|
tree.insert(z[size - 1])
|
||||||
MemoryManagerBTree.save_stats(size)
|
stats[size] = {
|
||||||
|
"comparisons": ctx.comparisons,
|
||||||
|
"writes": ctx.writes,
|
||||||
|
"loads": count_loads(tree.root),
|
||||||
|
"saves": count_saves(tree.root),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Einfaches Liniendiagramm über alle gespeicherten Metriken
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
x = list(stats.keys())
|
||||||
|
fig, axes = plt.subplots(len(stats[x[0]]), 1, figsize=(8, 12), sharex=True)
|
||||||
|
for ax, label in zip(axes, stats[x[0]].keys()):
|
||||||
|
ax.plot(x, [stats[k][label] for k in x], label=label)
|
||||||
|
ax.set_ylabel(label)
|
||||||
|
ax.legend()
|
||||||
|
plt.xlabel("n")
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.show()
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares", "loads", "saves"])
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sizes = range(1, 1001, 2)
|
sizes = range(1, 1001, 2)
|
||||||
|
|||||||
@@ -1,23 +1,29 @@
|
|||||||
from utils.literal import Literal
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_int import Int
|
||||||
from b_tree_node import BTreeNode
|
from b_tree_node import BTreeNode
|
||||||
|
|
||||||
|
|
||||||
class BTree:
|
class BTree:
|
||||||
def __init__(self, m: int):
|
|
||||||
|
def __init__(self, m: int, ctx: AlgoContext):
|
||||||
self.m = m
|
self.m = m
|
||||||
self.root = BTreeNode(m)
|
self.ctx = ctx
|
||||||
|
self.root = BTreeNode(m, ctx)
|
||||||
|
|
||||||
|
def _new_node(self):
|
||||||
|
return BTreeNode(self.m, self.ctx)
|
||||||
|
|
||||||
def search(self, value, start: BTreeNode = None) -> BTreeNode | None:
|
def search(self, value, start: BTreeNode = None) -> BTreeNode | None:
|
||||||
if not start:
|
if not start:
|
||||||
start = self.root
|
start = self.root
|
||||||
start.load()
|
start.load()
|
||||||
|
if not isinstance(value, Int):
|
||||||
|
value = Int(value, self.ctx)
|
||||||
i = 0
|
i = 0
|
||||||
if not isinstance(value, MemoryCell):
|
while i < start.n and value > start.value[i]:
|
||||||
value = MemoryCell(value)
|
|
||||||
while i < start.n and value > start.value[Literal(i)]:
|
|
||||||
i += 1
|
i += 1
|
||||||
if i < start.n and value == start.value[Literal(i)]:
|
if i < start.n and value == start.value[i]:
|
||||||
return start
|
return start
|
||||||
if start.leaf:
|
if start.leaf:
|
||||||
return None
|
return None
|
||||||
@@ -26,11 +32,11 @@ class BTree:
|
|||||||
def split_child(self, parent: BTreeNode, i: int):
|
def split_child(self, parent: BTreeNode, i: int):
|
||||||
child = parent.children[i]
|
child = parent.children[i]
|
||||||
child.load()
|
child.load()
|
||||||
h = BTreeNode(self.m)
|
h = self._new_node()
|
||||||
h.leaf = child.leaf
|
h.leaf = child.leaf
|
||||||
h.n = self.m - 1
|
h.n = self.m - 1
|
||||||
for j in range(self.m - 1):
|
for j in range(self.m - 1):
|
||||||
h.value[Literal(j)] = child.value[Literal(j + self.m)]
|
h.value[j] = child.value[j + self.m]
|
||||||
if not h.leaf:
|
if not h.leaf:
|
||||||
for j in range(self.m):
|
for j in range(self.m):
|
||||||
h.children[j] = child.children[j + self.m]
|
h.children[j] = child.children[j + self.m]
|
||||||
@@ -41,18 +47,18 @@ class BTree:
|
|||||||
h.save()
|
h.save()
|
||||||
for j in range(parent.n, i, -1):
|
for j in range(parent.n, i, -1):
|
||||||
parent.children[j + 1] = parent.children[j]
|
parent.children[j + 1] = parent.children[j]
|
||||||
parent.value[Literal(j)] = parent.value[Literal(j - 1)]
|
parent.value[j] = parent.value[j - 1]
|
||||||
parent.children[i + 1] = h
|
parent.children[i + 1] = h
|
||||||
parent.value[Literal(i)] = child.value[Literal(self.m - 1)]
|
parent.value[i] = child.value[self.m - 1]
|
||||||
parent.n += 1
|
parent.n += 1
|
||||||
parent.save()
|
parent.save()
|
||||||
|
|
||||||
def insert(self, value):
|
def insert(self, value):
|
||||||
if not isinstance(value, MemoryCell):
|
if not isinstance(value, Int):
|
||||||
value = MemoryCell(value)
|
value = Int(value, self.ctx)
|
||||||
r = self.root
|
r = self.root
|
||||||
if r.n == 2 * self.m - 1:
|
if r.n == 2 * self.m - 1:
|
||||||
h = BTreeNode(self.m)
|
h = self._new_node()
|
||||||
self.root = h
|
self.root = h
|
||||||
h.leaf = False
|
h.leaf = False
|
||||||
h.n = 0
|
h.n = 0
|
||||||
@@ -62,44 +68,40 @@ class BTree:
|
|||||||
else:
|
else:
|
||||||
self.insert_in_node(r, value)
|
self.insert_in_node(r, value)
|
||||||
|
|
||||||
def insert_in_node(self, start: BTreeNode, value):
|
def insert_in_node(self, start: BTreeNode, value: Int):
|
||||||
start.load()
|
start.load()
|
||||||
i = start.n
|
i = start.n
|
||||||
if start.leaf:
|
if start.leaf:
|
||||||
while i >= 1 and value < start.value[Literal(i-1)]:
|
while i >= 1 and value < start.value[i - 1]:
|
||||||
start.value[Literal(i)] = start.value[Literal(i-1)]
|
start.value[i] = start.value[i - 1]
|
||||||
i -= 1
|
i -= 1
|
||||||
start.value[Literal(i)].set(value)
|
start.value[i] = value
|
||||||
start.n += 1
|
start.n += 1
|
||||||
start.save()
|
start.save()
|
||||||
else:
|
else:
|
||||||
j = 0
|
j = 0
|
||||||
while j < start.n and value > start.value[Literal(j)]:
|
while j < start.n and value > start.value[j]:
|
||||||
j += 1
|
j += 1
|
||||||
if start.children[j].n == 2 * self.m - 1:
|
if start.children[j].n == 2 * self.m - 1:
|
||||||
self.split_child(start, j)
|
self.split_child(start, j)
|
||||||
if value > start.value[Literal(j)]:
|
if value > start.value[j]:
|
||||||
j += 1
|
j += 1
|
||||||
self.insert_in_node(start.children[j], value)
|
self.insert_in_node(start.children[j], value)
|
||||||
|
|
||||||
def traversal(self, callback):
|
def traversal(self, callback):
|
||||||
def traversal_recursive(node, callback):
|
def _rec(node, callback):
|
||||||
i = 0
|
i = 0
|
||||||
while i < node.n:
|
while i < node.n:
|
||||||
if not node.leaf:
|
if not node.leaf:
|
||||||
traversal_recursive(node.children[i], callback)
|
_rec(node.children[i], callback)
|
||||||
callback(node.value[Literal(i)])
|
callback(node.value[i])
|
||||||
i += 1
|
i += 1
|
||||||
if not node.leaf:
|
if not node.leaf:
|
||||||
traversal_recursive(node.children[i], callback)
|
_rec(node.children[i], callback)
|
||||||
|
_rec(self.root, callback)
|
||||||
traversal_recursive(self.root, callback)
|
|
||||||
|
|
||||||
def walk(self):
|
def walk(self):
|
||||||
def print_key(key):
|
self.traversal(lambda key: print(key, end=" "))
|
||||||
print(key, end=" ")
|
|
||||||
|
|
||||||
self.traversal(print_key)
|
|
||||||
|
|
||||||
def height(self, start: BTreeNode = None):
|
def height(self, start: BTreeNode = None):
|
||||||
if not start:
|
if not start:
|
||||||
@@ -110,8 +112,9 @@ class BTree:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
a = MemoryArray.create_array_from_file("data/seq3.txt")
|
ctx = AlgoContext()
|
||||||
tree = BTree(3)
|
a = Array.from_file("data/seq3.txt", ctx)
|
||||||
|
tree = BTree(3, ctx)
|
||||||
for cell in a:
|
for cell in a:
|
||||||
tree.insert(cell)
|
tree.insert(cell)
|
||||||
print(f"Height: {tree.height()}")
|
print(f"Height: {tree.height()}")
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
from utils.literal import Literal
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_array import Array
|
||||||
from utils.memory_array import MemoryArray
|
|
||||||
|
|
||||||
class BTreeNode(MemoryCell):
|
|
||||||
|
|
||||||
def __init__(self, m: int):
|
class BTreeNode:
|
||||||
super().__init__()
|
"""
|
||||||
|
Knoten eines B-Baums.
|
||||||
|
|
||||||
|
value – Array der Schlüssel (Kapazität: 2m-1)
|
||||||
|
n – Anzahl aktuell gespeicherter Schlüssel
|
||||||
|
leaf – True wenn Blattknoten
|
||||||
|
loaded_count / saved_count – Disk-I/O-Zähler für externe Komplexitätsanalyse
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, m: int, ctx: AlgoContext):
|
||||||
self.m = m
|
self.m = m
|
||||||
|
self.ctx = ctx
|
||||||
self.n = 0
|
self.n = 0
|
||||||
self.leaf = True
|
self.leaf = True
|
||||||
self.value = MemoryArray(Literal(2 * m - 1))
|
self.value = Array([0] * (2 * m - 1), ctx)
|
||||||
self.children = [None] * (2 * m)
|
self.children = [None] * (2 * m)
|
||||||
self.loaded_count = 0
|
self.loaded_count = 0
|
||||||
self.saved_count = 0
|
self.saved_count = 0
|
||||||
|
|
||||||
def reset_counters(self):
|
|
||||||
super().reset_counters()
|
|
||||||
self.loaded_count = 0
|
|
||||||
self.saved_count = 0
|
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
self.loaded_count += 1
|
self.loaded_count += 1
|
||||||
|
|
||||||
@@ -26,4 +29,4 @@ class BTreeNode(MemoryCell):
|
|||||||
self.saved_count += 1
|
self.saved_count += 1
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "(" + " ".join([str(self.value[Literal(i)]) for i in range(self.n)]) + ")"
|
return "(" + " ".join([str(self.value[i]) for i in range(self.n)]) + ")"
|
||||||
@@ -1,58 +1,53 @@
|
|||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
from utils.literal import Literal
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_int import Int
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_array import Array
|
||||||
from utils.memory_manager import MemoryManager
|
|
||||||
from vorlesung.L07_hashtable.hashtable import HashTableOpenAddressing
|
from vorlesung.L07_hashtable.hashtable import HashTableOpenAddressing
|
||||||
|
|
||||||
#Goldener Schnitt
|
# Goldener Schnitt (Konstante, nicht instrumentiert)
|
||||||
a = Literal((math.sqrt(5) - 1) / 2)
|
_A = (math.sqrt(5) - 1) / 2
|
||||||
|
|
||||||
# Hashfunktion nach multiplikativer Methode
|
|
||||||
def h(x: MemoryCell, m: Literal) -> Literal:
|
|
||||||
with MemoryCell(int(x * a)) as integer_part, MemoryCell(x * a) as full_product:
|
|
||||||
with MemoryCell(full_product - integer_part) as fractional_part:
|
|
||||||
return Literal(abs(int(fractional_part * m)))
|
|
||||||
|
|
||||||
# Quadratische Sondierung
|
def h(x: Int, m: Int) -> Int:
|
||||||
def f(x: MemoryCell, i: Literal, m: Literal) -> Literal:
|
"""Hashfunktion nach multiplikativer Methode."""
|
||||||
c1 = 1
|
full = x.value * _A
|
||||||
c2 = 5
|
return Int(int(abs(full - int(full)) * int(m)), x._ctx)
|
||||||
with MemoryCell(h(x, m)) as initial_hash, MemoryCell(c2 * int(i) * int(i)) as quadratic_offset:
|
|
||||||
with MemoryCell(initial_hash + quadratic_offset) as probe_position:
|
|
||||||
probe_position += Literal(c1 * int(i)) # Linear component
|
|
||||||
return probe_position % m
|
|
||||||
|
|
||||||
# Symmetrische quadratische Sondierung
|
|
||||||
def fs(x: MemoryCell, i: Literal, m: Literal) -> Literal:
|
def f(x: Int, i: Int, m: Int) -> Int:
|
||||||
with MemoryCell(h(x, m)) as base_hash, MemoryCell(int(i) * int(i)) as square:
|
"""Quadratische Sondierung."""
|
||||||
if int(i) % 2 == 0: # gerades i: Vorwärtssondierung
|
c1, c2 = 1, 5
|
||||||
with MemoryCell(base_hash + square) as position:
|
base = int(h(x, m))
|
||||||
return position % m
|
probe = base + c1 * int(i) + c2 * int(i) ** 2
|
||||||
else: # ungerades i: Rückwärtssondierung
|
return Int(probe % int(m), x._ctx)
|
||||||
with MemoryCell(base_hash - square) as position:
|
|
||||||
return position % m
|
|
||||||
|
def fs(x: Int, i: Int, m: Int) -> Int:
|
||||||
|
"""Symmetrische quadratische Sondierung."""
|
||||||
|
base = int(h(x, m))
|
||||||
|
sq = int(i) ** 2
|
||||||
|
if int(i) % 2 == 0:
|
||||||
|
probe = base + sq
|
||||||
|
else:
|
||||||
|
probe = base - sq
|
||||||
|
return Int(probe % int(m), x._ctx)
|
||||||
|
|
||||||
|
|
||||||
def analyze_complexity(sizes):
|
def analyze_complexity(sizes):
|
||||||
"""
|
ctx = AlgoContext()
|
||||||
Analysiert die Komplexität
|
|
||||||
|
|
||||||
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
|
|
||||||
"""
|
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
MemoryManager.purge() # Speicher zurücksetzen
|
ctx.reset()
|
||||||
ht = HashTableOpenAddressing(size, f)
|
ht = HashTableOpenAddressing(size, f, ctx)
|
||||||
random_array = MemoryArray.create_random_array(size, -100, 100)
|
z = Array.random(size, -100, 100, ctx)
|
||||||
for cell in random_array:
|
for cell in z:
|
||||||
ht.insert(cell)
|
ht.insert(cell)
|
||||||
MemoryManager.reset()
|
ctx.reset()
|
||||||
cell = random.choice(random_array.cells)
|
target = z[random.randint(0, size - 1)]
|
||||||
ht.search(cell)
|
ht.search(target)
|
||||||
MemoryManager.save_stats(size)
|
ctx.save_stats(size)
|
||||||
|
|
||||||
MemoryManager.plot_stats(["cells", "compares"])
|
ctx.plot_stats(["comparisons"])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,76 +1,75 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from utils.literal import Literal
|
from utils.algo_context import AlgoContext
|
||||||
from utils.memory_array import MemoryArray
|
from utils.algo_array import Array
|
||||||
from utils.memory_cell import MemoryCell
|
from utils.algo_int import Int
|
||||||
from utils.memory_range import mrange
|
from utils.algo_range import Range
|
||||||
|
|
||||||
|
|
||||||
UNUSED_MARK = "UNUSED"
|
UNUSED_MARK = "UNUSED"
|
||||||
DELETED_MARK = "DELETED"
|
DELETED_MARK = "DELETED"
|
||||||
|
|
||||||
class HashTableOpenAddressing:
|
|
||||||
def __init__(self, m: Literal, f: Callable[[MemoryCell, Literal, Literal], Literal]):
|
|
||||||
if not isinstance(m, Literal):
|
|
||||||
m = Literal(m)
|
|
||||||
self.m = m
|
|
||||||
self.f = f
|
|
||||||
self.table = MemoryArray(m)
|
|
||||||
for i in mrange(m):
|
|
||||||
self.table[i].value = UNUSED_MARK
|
|
||||||
|
|
||||||
def insert(self, x: MemoryCell):
|
class HashTableOpenAddressing:
|
||||||
with MemoryCell(0) as i:
|
"""
|
||||||
|
Hashtabelle mit offener Adressierung.
|
||||||
|
|
||||||
|
f – Sondierungsfunktion f(x: Int, i: Int, m: Int) -> Int
|
||||||
|
Liefert die Tabellenposition für Schlüssel x beim i-ten Versuch.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, m: int, f: Callable[[Int, Int, Int], Int], ctx: AlgoContext):
|
||||||
|
self.ctx = ctx
|
||||||
|
self.m = Int(m, ctx)
|
||||||
|
self.f = f
|
||||||
|
self.table = Array([UNUSED_MARK] * m, ctx)
|
||||||
|
|
||||||
|
def insert(self, x: Int) -> bool:
|
||||||
|
i = Int(0, self.ctx)
|
||||||
while i < self.m:
|
while i < self.m:
|
||||||
j = self.f(x, i, self.m)
|
j = self.f(x, i, self.m)
|
||||||
if self.is_free(j):
|
if self.is_free(j):
|
||||||
self.table[j].set(x)
|
self.table[j] = x
|
||||||
return True
|
return True
|
||||||
i.set(i.succ())
|
i += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def search(self, x: MemoryCell):
|
def search(self, x: Int) -> bool:
|
||||||
with MemoryCell(0) as i:
|
i = Int(0, self.ctx)
|
||||||
while i < self.m:
|
while i < self.m:
|
||||||
j = self.f(x, i, self.m)
|
j = self.f(x, i, self.m)
|
||||||
if self.is_unused(j):
|
if self.is_unused(j):
|
||||||
return False
|
return False
|
||||||
if self.table[j] == x:
|
if self.table[j] == x:
|
||||||
return True
|
return True
|
||||||
i.set(i.succ())
|
i += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def delete(self, x: MemoryCell):
|
def delete(self, x: Int) -> bool:
|
||||||
with MemoryCell(0) as i:
|
i = Int(0, self.ctx)
|
||||||
while i < self.m:
|
while i < self.m:
|
||||||
j = self.f(x, i, self.m)
|
j = self.f(x, i, self.m)
|
||||||
if self.is_unused(j):
|
if self.is_unused(j):
|
||||||
return False
|
return False
|
||||||
if self.table[j] == x:
|
if self.table[j] == x:
|
||||||
self.table[j].value = DELETED_MARK
|
self.table[j].set(DELETED_MARK) # Tombstone setzen (1 write)
|
||||||
return True
|
return True
|
||||||
i.set(i.succ())
|
i += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.table)
|
return str(self.table)
|
||||||
|
|
||||||
def alpha(self):
|
def alpha(self) -> float:
|
||||||
with MemoryCell(0) as i:
|
"""Belegungsfaktor der Tabelle."""
|
||||||
used = 0
|
used = sum(0 if self.is_free(Int(i, self.ctx)) else 1
|
||||||
while i < self.m:
|
for i in range(int(self.m)))
|
||||||
used += 0 if self.is_free(i) else 1
|
|
||||||
i.set(i.succ())
|
|
||||||
return used / int(self.m)
|
return used / int(self.m)
|
||||||
|
|
||||||
def is_unused(self, i: Literal):
|
def is_unused(self, i: Int) -> bool:
|
||||||
if self.table[i].value == UNUSED_MARK:
|
return self.table[i].value == UNUSED_MARK
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_deleted(self, i: Literal):
|
def is_deleted(self, i: Int) -> bool:
|
||||||
if self.table[i].value == DELETED_MARK:
|
return self.table[i].value == DELETED_MARK
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_free(self, i: Literal):
|
def is_free(self, i: Int) -> bool:
|
||||||
return self.is_unused(i) or self.is_deleted(i)
|
return self.is_unused(i) or self.is_deleted(i)
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
from vorlesung.L08_graphen.graph import Graph, AdjacencyMatrixGraph
|
|
||||||
from utils.project_dir import get_path
|
|
||||||
|
|
||||||
graph = AdjacencyMatrixGraph()
|
|
||||||
start = ""
|
|
||||||
end = ""
|
|
||||||
|
|
||||||
def read_file(filename: str = "data/aoc2212.txt"):
|
|
||||||
"""Read a file and return the content as a string."""
|
|
||||||
|
|
||||||
def adjust_char(char):
|
|
||||||
"""Adjust character for comparison."""
|
|
||||||
if char == 'S':
|
|
||||||
return 'a'
|
|
||||||
elif char == 'E':
|
|
||||||
return 'z'
|
|
||||||
return char
|
|
||||||
|
|
||||||
global start, end
|
|
||||||
with open(get_path(filename), "r") as file:
|
|
||||||
quest = file.read().strip().splitlines()
|
|
||||||
for row, line in enumerate(quest):
|
|
||||||
for col, char in enumerate(line):
|
|
||||||
label = f"{row},{col}"
|
|
||||||
graph.insert_vertex(label)
|
|
||||||
if char == "S":
|
|
||||||
start = label
|
|
||||||
if char == "E":
|
|
||||||
end = label
|
|
||||||
for row, line in enumerate(quest):
|
|
||||||
for col, char in enumerate(line):
|
|
||||||
for neighbor in [(row - 1, col), (row, col - 1), (row + 1, col), (row, col + 1)]:
|
|
||||||
if 0 <= neighbor[0] < len(quest) and 0 <= neighbor[1] < len(line):
|
|
||||||
if ord(adjust_char(quest[neighbor[0]][neighbor[1]])) <= ord(adjust_char(char)) + 1:
|
|
||||||
label1 = f"{row},{col}"
|
|
||||||
label2 = f"{neighbor[0]},{neighbor[1]}"
|
|
||||||
graph.connect(label1, label2)
|
|
||||||
|
|
||||||
|
|
||||||
# Lösung des Adventskalenders 2022, Tag 12
|
|
||||||
read_file("data/aoc2212test.txt")
|
|
||||||
graph.graph()
|
|
||||||
distance_map, predecessor_map = graph.bfs(start)
|
|
||||||
print(distance_map[graph.get_vertex(end)])
|
|
||||||
print(graph.path(end, predecessor_map))
|
|
||||||
@@ -5,8 +5,8 @@ import graphviz
|
|||||||
import math
|
import math
|
||||||
import heapq
|
import heapq
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from utils.project_dir import get_path
|
from utils.algo_path import path
|
||||||
from utils.priority_queue import PriorityQueue
|
from utils.algo_priority_queue import PriorityQueue
|
||||||
from vorlesung.L09_mst.disjoint import DisjointValue
|
from vorlesung.L09_mst.disjoint import DisjointValue
|
||||||
|
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ class Graph:
|
|||||||
predecessor_map[vertex] = None
|
predecessor_map[vertex] = None
|
||||||
|
|
||||||
while white_vertices:
|
while white_vertices:
|
||||||
v = white_vertices.pop()
|
v = next(iter(white_vertices))
|
||||||
dfs_visit(v)
|
dfs_visit(v)
|
||||||
|
|
||||||
return enter_map, leave_map, predecessor_map
|
return enter_map, leave_map, predecessor_map
|
||||||
@@ -155,7 +155,7 @@ class Graph:
|
|||||||
dot.edge(str(id(self.get_vertex(edge[0]))), str(id(self.get_vertex(edge[1]))), label=str(edge[2]))
|
dot.edge(str(id(self.get_vertex(edge[0]))), str(id(self.get_vertex(edge[1]))), label=str(edge[2]))
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
filename = f"{filename}_{timestamp}.gv"
|
filename = f"{filename}_{timestamp}.gv"
|
||||||
filename = get_path(filename)
|
filename = path(filename)
|
||||||
dot.render(filename)
|
dot.render(filename)
|
||||||
|
|
||||||
def dijkstra(self, start_name: str) -> tuple[dict[Vertex, float], dict[Vertex, Vertex | None]]:
|
def dijkstra(self, start_name: str) -> tuple[dict[Vertex, float], dict[Vertex, Vertex | None]]:
|
||||||
|
|||||||
Reference in new issue
Block a user