Musterlösung P3
This commit is contained in:
parent
f0b82e726a
commit
24d15e1e44
127
praktika/03_quick_and_heap/priority_queue.py
Normal file
127
praktika/03_quick_and_heap/priority_queue.py
Normal file
@ -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()
|
||||
|
||||
88
praktika/03_quick_and_heap/quick_sort_median3.py
Normal file
88
praktika/03_quick_and_heap/quick_sort_median3.py
Normal file
@ -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()
|
||||
Loading…
x
Reference in New Issue
Block a user