From 24d15e1e4417e3f9ef39dd15436b20fa8167b430 Mon Sep 17 00:00:00 2001 From: Oliver Hofmann Date: Mon, 4 May 2026 10:39:08 +0200 Subject: [PATCH] =?UTF-8?q?Musterl=C3=B6sung=20P3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- praktika/03_quick_and_heap/priority_queue.py | 127 ++++++++++++++++++ .../03_quick_and_heap/quick_sort_median3.py | 88 ++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 praktika/03_quick_and_heap/priority_queue.py create mode 100644 praktika/03_quick_and_heap/quick_sort_median3.py diff --git a/praktika/03_quick_and_heap/priority_queue.py b/praktika/03_quick_and_heap/priority_queue.py new file mode 100644 index 0000000..7e9db76 --- /dev/null +++ b/praktika/03_quick_and_heap/priority_queue.py @@ -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() + diff --git a/praktika/03_quick_and_heap/quick_sort_median3.py b/praktika/03_quick_and_heap/quick_sort_median3.py new file mode 100644 index 0000000..f866bfa --- /dev/null +++ b/praktika/03_quick_and_heap/quick_sort_median3.py @@ -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()