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()