4.9 KiB
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
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
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.
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
insertion_sort(z, ctx)
print(ctx.comparisons) # Anzahl Vergleiche
print(ctx.reads) # Lesezugriffe
print(ctx.writes) # Schreibzugriffe
print(ctx.additions) # Additionen
Oder kompakt:
print(ctx.summary())
5. Komplexität über mehrere Eingabegrößen plotten
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:
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:
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)