Compare commits

...

13 Commits

Author SHA1 Message Date
Oliver Hofmann
add4091b5d elektro 2025-06-11 09:14:53 +02:00
Oliver Hofmann
6868e089a4 rendeer maze 2025-06-03 22:48:08 +02:00
Oliver Hofmann
4d581a87de aoc 2025-05-28 07:29:38 +02:00
Oliver Hofmann
9919f791af Höhle 2025-05-27 19:49:38 +02:00
Oliver Hofmann
f62cc0a1c8 Höhle 2025-05-21 06:13:36 +02:00
Oliver Hofmann
c8a4038795 Hashtable added 2025-05-13 13:00:07 +02:00
Oliver Hofmann
27ca711383 B-Tree 2025-05-13 12:58:04 +02:00
Oliver Hofmann
63a3460c21 switched to Graphviz module
Better filename
2025-05-03 11:26:40 +02:00
Oliver Hofmann
1c37ed46bf Merge branch 'main' of https://git.efi.th-nuernberg.de/gitea/hofmannol/AlgoDatSoSe25 2025-05-03 09:39:09 +02:00
Oliver Hofmann
2b2a97d1da AVL implementation 2025-05-03 09:39:02 +02:00
Oliver Hofmann
1df8100e14 AVL implementation 2025-05-03 09:37:22 +02:00
6605fe9957 check_circle removed 2025-04-30 13:23:29 +02:00
Oliver Hofmann
ac7068d26c AVL implementation 2025-04-29 18:19:35 +02:00
32 changed files with 1435 additions and 25 deletions

5
data/aoc2212test.txt Normal file
View File

@ -0,0 +1,5 @@
Sabqponm
abcryxxl
accszExk
acctuvwj
abdefghi

15
data/aoc2416test.txt Normal file
View File

@ -0,0 +1,15 @@
###############
#.......#....E#
#.#.###.#.###.#
#.....#.#...#.#
#.###.#####.#.#
#.#.#.......#.#
#.#.#####.###.#
#...........#.#
###.#.#####.#.#
#...#.....#.#.#
#.#.#.###.#.#.#
#.....#...#.#.#
#.###.#.#.#.#.#
#S..#.....#...#
###############

8
data/elektro.txt Normal file
View File

@ -0,0 +1,8 @@
"Höhleneingang";"Ost/West-Passage";5
"Höhleneingang";"Nord/Süd-Passage";3
"Nord/Süd-Passage";"Nebelraum";7
"Steiniger Pfad";"Ost/West-Passage";2
"Ost/West-Passage";"Schwefelgewölbe";4
"Schwefelgewölbe";"Steiniger Pfad";1
"Schatzkammer";"Nebelraum";2
"Steiniger Pfad";"Schatzkammer";6

8
data/hoehle.txt Normal file
View File

@ -0,0 +1,8 @@
"Höhleneingang" <> "Ost/West-Passage"
"Höhleneingang" <> "Nord/Süd-Passage"
"Nord/Süd-Passage" <> "Nebelraum"
"Steiniger Pfad" > "Ost/West-Passage"
"Ost/West-Passage" <> "Schwefelgewölbe"
"Schwefelgewölbe" > "Steiniger Pfad"
"Schatzkammer" > "Nebelraum"
"Steiniger Pfad" > "Schatzkammer"

9
data/labyrinth.txt Normal file
View File

@ -0,0 +1,9 @@
xxxxxxxxxxxxxxxxxxxxx
x x
x S x
x x
x xxxxxxxx x
x x
x x
x A x
xxxxxxxxxxxxxxxxxxxxx

5
data/labyrinth1.txt Normal file
View File

@ -0,0 +1,5 @@
xxxxxAxxxxxxxxx
x xSx
xxxxxxxxxx xx x
x x
xxxxxxxxxxxxxxx

View File

@ -0,0 +1,12 @@
from utils.memory_array import MemoryArray
from vorlesung.L05_binaere_baeume.avl_tree import AVLTree
if __name__ == "__main__":
a = MemoryArray.create_array_from_file("data/seq0.txt")
tree = AVLTree()
for cell in a:
tree.insert(int(cell))
tree.graph_traversal()

View File

@ -0,0 +1,12 @@
from utils.memory_array import MemoryArray
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
if __name__ == "__main__":
a = MemoryArray.create_array_from_file("data/seq0.txt")
tree = BinaryTree()
for cell in a:
tree.insert(int(cell))
tree.graph_traversal()

View File

@ -0,0 +1,106 @@
import math
import unittest
from utils.literal import Literal
from utils.memory_cell import MemoryCell
from utils.memory_array import MemoryArray
from vorlesung.L07_hashtable.hashtable import HashTableOpenAddressing
#Goldener Schnitt
a = Literal((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 f(x: MemoryCell, i: Literal, m: Literal) -> Literal:
c1 = 1
c2 = 5
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:
with MemoryCell(h(x, m)) as base_hash, MemoryCell(int(i) * int(i)) as square:
if int(i) % 2 == 0: # gerades i: Vorwärtssondierung
with MemoryCell(base_hash + square) as position:
return position % m
else: # ungerades i: Rückwärtssondierung
with MemoryCell(base_hash - square) as position:
return position % m
if __name__ == "__main__":
print("*** Aufgabe 3 ***")
size = Literal(20)
print(f"Anlage einer Hash-Tabelle mit offener Adressierung mit Größe {size}")
ht = HashTableOpenAddressing(size, f)
print("Einfügen der Werte aus seq0.txt")
for cell in MemoryArray.create_array_from_file("data/seq0.txt"):
if not ht.insert(cell):
print(f"Einfügen von {cell} nicht möglich")
print(ht)
print(f"Belegungsfaktor: {ht.alpha()}")
with MemoryCell(52) as cell:
print(f"Suche nach {cell}")
if ht.search(cell):
print(f"{cell} gefunden, wird gelöscht.")
ht.delete(cell)
else:
print(f"{cell} nicht gefunden")
print(ht)
print(f"Belegungsfaktor: {ht.alpha()}")
print("Einfügen von 24")
with MemoryCell(24) as cell:
if not ht.insert(cell):
print(f"Einfügen von {cell} nicht möglich")
print(ht)
print(f"Belegungsfaktor: {ht.alpha()}")
print()
print("*** Aufgabe 4 ***")
size = Literal(90)
print(f"Anlage einer Hash-Tabelle mit offener Adressierung mit Größe {size}")
ht = HashTableOpenAddressing(size, f)
print("Einfügen der Werte aus seq1.txt")
for cell in MemoryArray.create_array_from_file("data/seq1.txt"):
if not ht.insert(cell):
print(f"Einfügen von {cell} nicht möglich")
print(ht)
print(f"Belegungsfaktor: {ht.alpha()}")
print()
print("*** Aufgabe 5 ***")
size = Literal(89)
print(f"Anlage einer Hash-Tabelle mit offener Adressierung mit Größe {size}")
ht = HashTableOpenAddressing(size, f)
print("Einfügen der Werte aus seq1.txt")
for cell in MemoryArray.create_array_from_file("data/seq1.txt"):
if not ht.insert(cell):
print(f"Einfügen von {cell} nicht möglich")
print(ht)
print(f"Belegungsfaktor: {ht.alpha()}")
print()
print("*** Aufgabe 6 ***")
size = Literal(90)
print(f"Anlage einer Hash-Tabelle mit offener Adressierung mit Größe {size}")
print("Verwendung der symmetrischen quadratischen Sondierung")
ht = HashTableOpenAddressing(size, fs)
print("Einfügen der Werte aus seq1.txt")
for cell in MemoryArray.create_array_from_file("data/seq1.txt"):
if not ht.insert(cell):
print(f"Einfügen von {cell} nicht möglich")
print(ht)
print(f"Belegungsfaktor: {ht.alpha()}")
print()

View File

@ -0,0 +1,37 @@
import re
from utils.project_dir import get_path
from vorlesung.L08_graphen.graph import Graph, AdjacencyListGraph, AdjacencyMatrixGraph
def read_cave_into_graph(graph: Graph, filename: str):
"""Read a cave description from a file and insert it into the given graph."""
filename = get_path(filename)
with open(filename, "r") as file:
lines = file.readlines()
for line in lines:
# match a line with two node names and an optional direction
m = re.match(r"(^\s*\"(.*)\"\s*([<>]*)\s*\"(.*)\"\s*)", line)
if m:
startnode = m.group(2)
endnode = m.group(4)
opcode = m.group(3)
graph.insert_vertex(startnode)
graph.insert_vertex(endnode)
if '>' in opcode:
graph.connect(startnode, endnode)
if '<' in opcode:
graph.connect(endnode, startnode)
graph = AdjacencyListGraph()
# graph = AdjacencyMatrixGraph()
read_cave_into_graph(graph, "data/hoehle.txt")
_, predecessor_map = graph.bfs('Höhleneingang')
path = graph.path('Schatzkammer', predecessor_map)
print(path)
_, predecessor_map = graph.bfs('Schatzkammer')
path = graph.path('Höhleneingang', predecessor_map)
print(path)
graph.graph("Höhle")

View File

@ -0,0 +1,59 @@
from utils.project_dir import get_path
from vorlesung.L08_graphen.graph import AdjacencyListGraph
DIRECTIONS = [(1,0), (0, 1), (-1, 0), (0, -1)]
class D16Solution:
def __init__(self):
with open(get_path("data/aoc2416.txt"), "r") as file:
self.puzzle = [line.strip() for line in file]
self.cells = set()
self.start = None
self.end = None
def get_cells_start_end(self):
for row in range(len(self.puzzle)):
for col in range(len(self.puzzle[row])):
if self.puzzle[row][col] != '#':
self.cells.add((col, row))
if self.puzzle[row][col] == 'S':
self.start = (col, row)
if self.puzzle[row][col] == 'E':
self.end = (col, row)
def get_label(self, cell, direction):
"""Generate a label for a cell based on its coordinates and direction."""
return f"{cell[0]},{cell[1]}_{direction}"
def create_graph(self):
graph = AdjacencyListGraph()
for cell in self.cells:
for direction in DIRECTIONS:
label = self.get_label(cell, direction)
graph.insert_vertex(label)
for cell in self.cells:
for d, direction in enumerate(DIRECTIONS):
label = self.get_label(cell, direction)
dx, dy = direction
neighbor = (cell[0] + dx, cell[1] + dy)
if neighbor in self.cells:
graph.connect(label, self.get_label(neighbor, direction))
direction_left = DIRECTIONS[(d - 1) % 4]
direction_right = DIRECTIONS[(d + 1) % 4]
graph.connect(label, self.get_label(cell, direction_left), 1000)
graph.connect(label, self.get_label(cell, direction_right), 1000)
return graph
if __name__ == "__main__":
solution = D16Solution()
solution.get_cells_start_end()
graph = solution.create_graph()
start = solution.get_label(solution.start, DIRECTIONS[0])
print(f"Start: {start}")
distance_map, predecessor_map = graph.dijkstra(start)
end_labels = [solution.get_label(solution.end, direction) for direction in DIRECTIONS]
end_vertices = [graph.get_vertex(label) for label in end_labels]
min_weight = min([distance_map[vertex] for vertex in end_vertices])
print(f"Minimum distance to End {solution.end}: {min_weight}")

View File

@ -0,0 +1,32 @@
from vorlesung.L08_graphen.graph import Graph, AdjacencyMatrixGraph
from utils.project_dir import get_path
import re
def read_elektro_into_graph(graph: Graph, filename: str):
pattern = re.compile(r'"([^"]+)";"([^"]+)";(\d+)')
with (open(filename, "r") as file):
for line in file:
m = pattern.match(line)
if m:
start_name = m.group(1)
end_name = m.group(2)
cost = int(m.group(3))
graph.insert_vertex(start_name)
graph.insert_vertex(end_name)
graph.connect(start_name, end_name, cost)
graph.connect(end_name, start_name, cost)
if __name__ == "__main__":
graph = AdjacencyMatrixGraph()
read_elektro_into_graph(graph, get_path("data/elektro.txt"))
parents, cost = graph.mst_prim()
print(f"Kosten nach Prim: {cost}")
for node, parent in parents.items():
if parent is not None:
print(f"{node} - {parent}")
edges, cost = graph.mst_kruskal()
print(f"Kosten nach Kruskal: {cost}")
for start_name, end_name, _ in edges:
print(f"{start_name} - {end_name}")

View File

@ -1,3 +1,4 @@
matplotlib matplotlib
numpy numpy
pygame pygame
graphviz

40
utils/priority_queue.py Normal file
View File

@ -0,0 +1,40 @@
import heapq
class PriorityQueue:
def __init__(self):
self.heap = []
self.entry_finder = {} # map: item -> [priority, item]
self.REMOVED = '<removed>'
self.counter = 0 # unique sequence count to break ties
def add_or_update(self, item, priority):
if item in self.entry_finder:
self.remove(item)
count = self.counter
entry = [priority, count, item]
self.entry_finder[item] = entry
heapq.heappush(self.heap, entry)
self.counter += 1
def remove(self, item):
entry = self.entry_finder.pop(item)
entry[-1] = self.REMOVED # mark as removed
def pop(self):
while self.heap:
priority, count, item = heapq.heappop(self.heap)
if item != self.REMOVED:
del self.entry_finder[item]
return item, priority
return None
if __name__ == "__main__":
pq = PriorityQueue()
pq.add_or_update('task1', 1)
pq.add_or_update('task2', float('inf'))
pq.add_or_update('task3', float('inf'))
print(pq.pop()) # Should print ('task1', 1)
pq.add_or_update('task2', 0) # Update priority of 'task1'
print(pq.pop()) # Should print ('task2', 0)
print(pq.pop()) # Should print ('task3', 3)

View File

@ -0,0 +1,26 @@
from utils.memory_manager import MemoryManager
from utils.memory_array import MemoryArray
from utils.literal import Literal
from vorlesung.L05_binaere_baeume.avl_tree import AVLTree
def analyze_complexity(sizes):
"""
Analysiert die Komplexität
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
"""
for size in sizes:
MemoryManager.purge() # Speicher zurücksetzen
tree = AVLTree()
random_array = MemoryArray.create_random_array(size, -100, 100)
for i in range(size-1):
tree.insert(int(random_array[Literal(i)]))
MemoryManager.reset()
tree.insert(int(random_array[Literal(size-1)]))
MemoryManager.save_stats(size)
MemoryManager.plot_stats(["cells", "compares"])
if __name__ == "__main__":
sizes = range(1, 1001, 2)
analyze_complexity(sizes)

View File

@ -0,0 +1,94 @@
from utils.memory_array import MemoryArray
from vorlesung.L05_binaere_baeume.avl_tree_node import AVLTreeNode
from vorlesung.L05_binaere_baeume.bin_tree import BinaryTree
import logging
class AVLTree(BinaryTree):
def __init__(self):
super().__init__()
def new_node(self, value):
return AVLTreeNode(value)
def balance(self, node: AVLTreeNode):
node.update_balance()
if node.balance == -2:
if node.left.balance <= 0:
node = node.right_rotate()
else:
node = node.left_right_rotate()
elif node.balance == 2:
if node.right.balance >= 0:
node = node.left_rotate()
else:
node = node.right_left_rotate()
if node.parent:
self.balance(node.parent)
else:
self.root = node
def insert(self, value):
insert_generator = self.insert_stepwise(value)
node, parent = None, None
while True:
try:
node, parent = next(insert_generator)
except StopIteration:
break
return node, parent
def insert_stepwise(self, value):
node, parent = super().insert(value)
yield None, None
node.parent = parent
if parent:
self.balance(parent)
return node, parent
def delete(self, value):
node, parent = super().delete(value)
if node:
node.parent = parent
if parent:
self.balance(parent)
def graph_filename(self):
return "AVLTree"
if __name__ == "__main__":
def print_node(node, indent=0, level=0):
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:")
tree.in_order_traversal(print_node)
print("\nLevel-order traversal:")
tree.level_order_traversal(print_node)
print("\nTree structure traversal:")
tree.tree_structure_traversal(print_node)
print("\nGraph traversal:")
tree.graph_traversal()
tree.insert(9)
tree.graph_traversal()
print("\nDeleting 5:")
tree.delete(5)
print("In-order traversal after deletion:")
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)

View File

@ -0,0 +1,59 @@
import random
import pygame
from utils.game import Game
from avl_tree import AVLTree
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
WIDTH = 800
HEIGHT = 400
MARGIN = 20
class AVLTreeGame(Game):
def __init__(self):
super().__init__("AVLTree Game", fps=10, size=(WIDTH, HEIGHT))
random.seed()
self.z = list(range(1, 501))
random.shuffle(self.z)
self.finished = False
self.tree = AVLTree()
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.generator = None
def update_game(self):
if not self.finished:
if self.generator is None:
self.generator = self.tree.insert_stepwise(self.z.pop())
try:
next(self.generator)
except StopIteration:
self.generator = None
if self.generator is None and len(self.z) == 0:
self.finished = True
self.height = self.tree.get_height(self.tree.root)
return True
def draw_game(self):
self.screen.fill(WHITE)
if self.height > 0:
self.draw_tree(self.tree.root, WIDTH // 2, MARGIN, WIDTH // 4 - MARGIN)
super().draw_game()
def draw_tree(self, node, x, y, x_offset):
y_offset = (HEIGHT - (2 * MARGIN)) / self.height
if node is not None:
pygame.draw.circle(self.screen, BLUE, (x, y), 2)
if node.left is not None:
pygame.draw.line(self.screen, BLACK, (x, y), (x - x_offset, y + y_offset))
self.draw_tree(node.left, x - x_offset, y + y_offset, x_offset // 2)
if node.right is not None:
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)
if __name__ == "__main__":
tree_game = AVLTreeGame()
tree_game.run()

View File

@ -0,0 +1,61 @@
from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode
class AVLTreeNode(BinaryTreeNode):
def __init__(self, value):
super().__init__(value)
self.parent = None
self.balance = 0
def __repr__(self):
return f"TreeNode(id={id(self)} value={self.value}, left={self.left}, right={self.right})"
def graphviz_rep(self, row, col, dot):
dot.node(str(id(self)), label=str(self.value), pos=f"{col},{-row}!", xlabel=str(self.balance))
def update_balance(self):
left_height = self.left.height() if self.left else 0
right_height = self.right.height() if self.right else 0
self.balance = right_height - left_height
def right_rotate(self):
new_root = self.left
new_root.parent = self.parent
self.left = new_root.right
if self.left:
self.left.parent = self
new_root.right = self
self.parent = new_root
if new_root.parent:
if new_root.parent.left is self:
new_root.parent.left = new_root
else:
new_root.parent.right = new_root
self.update_balance()
new_root.update_balance()
return new_root
def left_rotate(self):
new_root = self.right
new_root.parent = self.parent
self.right = new_root.left
if self.right:
self.right.parent = self
new_root.left = self
self.parent = new_root
if new_root.parent:
if new_root.parent.left is self:
new_root.parent.left = new_root
else:
new_root.parent.right = new_root
self.update_balance()
new_root.update_balance()
return new_root
def right_left_rotate(self):
self.right = self.right.right_rotate()
return self.left_rotate()
def left_right_rotate(self):
self.left = self.left.left_rotate()
return self.right_rotate()

View File

@ -1,8 +1,7 @@
from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode
from utils.memory_manager import MemoryManager
from utils.memory_array import MemoryArray
from utils.project_dir import get_path from utils.project_dir import get_path
from datetime import datetime from datetime import datetime
import graphviz
class BinaryTree: class BinaryTree:
@ -69,7 +68,7 @@ class BinaryTree:
break break
else: else:
# Wert nicht gefunden # Wert nicht gefunden
return 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):
@ -144,37 +143,35 @@ class BinaryTree:
line = 0 line = 0
tree_structure_traversal_recursive(callback, self.root, 0) tree_structure_traversal_recursive(callback, self.root, 0)
def graph_filename(self):
return "BinaryTree"
def graph_traversal(self): def graph_traversal(self):
def define_node(node, level, line): def define_node(node, level, line):
nonlocal file nonlocal dot
if node is not None: if node is not None:
file.write(node.gv_rep(level, line)) node.graphviz_rep(level, line, dot)
def graph_traversal_recursive(current): def graph_traversal_recursive(current):
nonlocal file nonlocal dot
if current is not None: if current is not None:
if current.left: if current.left:
file.write(f"{id(current)} -> {id(current.left)}; \n") dot.edge(str(id(current)), str(id(current.left)))
graph_traversal_recursive(current.left) graph_traversal_recursive(current.left)
if current.right: if current.right:
file.write(f"{id(current)} -> {id(current.right)}; \n") dot.edge(str(id(current)), str(id(current.right)))
graph_traversal_recursive(current.right) graph_traversal_recursive(current.right)
timestamp = datetime.now().strftime("%Y%m%d_%H:%M:%S") dot = graphviz.Digraph( name="BinaryTree",
filename = f"graph_{timestamp}.gv" engine="neato",
filename = get_path(filename) node_attr={"shape": "circle", "fontname": "Arial"},
with open(filename, "w") as file: format="pdf" )
file.write("digraph BST {\n")
file.write("layout=neato;\n")
file.write("node [shape=circle, fontname=\"Arial\"];\n")
self.tree_structure_traversal(define_node) self.tree_structure_traversal(define_node)
graph_traversal_recursive(self.root) graph_traversal_recursive(self.root)
file.write("}") timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{self.graph_filename()}_{timestamp}.gv"
filename = get_path(filename)
dot.render(filename)
if __name__ == "__main__": if __name__ == "__main__":
tree = BinaryTree() tree = BinaryTree()

View File

@ -0,0 +1,54 @@
import random
import pygame
from utils.game import Game
from bin_tree import BinaryTree
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
WIDTH = 800
HEIGHT = 400
MARGIN = 20
class BinTreeGame(Game):
def __init__(self):
super().__init__("BinTree Game", fps=10, size=(WIDTH, HEIGHT))
random.seed()
self.z = list(range(1, 101))
random.shuffle(self.z)
self.finished = False
self.tree = BinaryTree()
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)
def update_game(self):
if not self.finished:
i = self.z.pop()
self.tree.insert(i)
self.height = self.tree.get_height(self.tree.root)
if len(self.z) == 0:
self.finished = True
return True
def draw_game(self):
self.screen.fill(WHITE)
if self.height > 0:
self.draw_tree(self.tree.root, WIDTH // 2, MARGIN, WIDTH // 4 - MARGIN)
super().draw_game()
def draw_tree(self, node, x, y, x_offset):
y_offset = (HEIGHT - (2 * MARGIN)) / self.height
if node is not None:
pygame.draw.circle(self.screen, BLUE, (x, y), 2)
if node.left is not None:
pygame.draw.line(self.screen, BLACK, (x, y), (x - x_offset, y + y_offset))
self.draw_tree(node.left, x - x_offset, y + y_offset, x_offset // 2)
if node.right is not None:
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)
if __name__ == "__main__":
tree_game = BinTreeGame()
tree_game.run()

View File

@ -18,7 +18,5 @@ class BinaryTreeNode(MemoryCell):
def __str__(self): def __str__(self):
return str(self.value) return str(self.value)
def gv_rep(self, row, col): def graphviz_rep(self, row, col, dot):
"""Returns the graphviz representation of the node.""" dot.node(str(id(self)), label=str(self.value), pos=f"{col},{-row}!")
return f"{id(self)} [label=\"{self.value}\", pos=\"{col},{-row}!\"];\n"

View File

@ -0,0 +1,58 @@
from utils.memory_manager import MemoryManager
from utils.memory_array import MemoryArray
from utils.literal import Literal
from b_tree import BTree
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():
return sum([cell.loaded_count for cell in MemoryManager().cells if isinstance(cell, BTreeNode)])
@staticmethod
def count_saves():
return sum([cell.saved_count for cell in MemoryManager().cells if isinstance(cell, BTreeNode)])
@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(),
"loads": MemoryManagerBTree.count_loads(),
"saves": MemoryManagerBTree.count_saves() }
MemoryManager.stats[count] = data
def analyze_complexity(sizes):
"""
Analysiert die Komplexität
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
"""
for size in sizes:
MemoryManager.purge() # Speicher zurücksetzen
tree = BTree(5)
random_array = MemoryArray.create_random_array(size, -100, 100)
for i in range(size-1):
tree.insert(int(random_array[Literal(i)]))
MemoryManager.reset()
tree.insert(int(random_array[Literal(size-1)]))
MemoryManagerBTree.save_stats(size)
MemoryManager.plot_stats(["cells", "compares", "loads", "saves"])
if __name__ == "__main__":
sizes = range(1, 1001, 2)
analyze_complexity(sizes)

View File

@ -0,0 +1,120 @@
from utils.literal import Literal
from utils.memory_cell import MemoryCell
from utils.memory_array import MemoryArray
from b_tree_node import BTreeNode
class BTree:
def __init__(self, m: int):
self.m = m
self.root = BTreeNode(m)
def search(self, value, start: BTreeNode = None) -> BTreeNode | None:
if not start:
start = self.root
start.load()
i = 0
if not isinstance(value, MemoryCell):
value = MemoryCell(value)
while i < start.n and value > start.value[Literal(i)]:
i += 1
if i < start.n and value == start.value[Literal(i)]:
return start
if start.leaf:
return None
return self.search(value, start.children[i])
def split_child(self, parent: BTreeNode, i: int):
child = parent.children[i]
child.load()
h = BTreeNode(self.m)
h.leaf = child.leaf
h.n = self.m - 1
for j in range(self.m - 1):
h.value[Literal(j)] = child.value[Literal(j + self.m)]
if not h.leaf:
for j in range(self.m):
h.children[j] = child.children[j + self.m]
for j in range(self.m, child.n + 1):
child.children[j] = None
child.n = self.m - 1
child.save()
h.save()
for j in range(parent.n, i, -1):
parent.children[j + 1] = parent.children[j]
parent.value[Literal(j)] = parent.value[Literal(j - 1)]
parent.children[i + 1] = h
parent.value[Literal(i)] = child.value[Literal(self.m - 1)]
parent.n += 1
parent.save()
def insert(self, value):
if not isinstance(value, MemoryCell):
value = MemoryCell(value)
r = self.root
if r.n == 2 * self.m - 1:
h = BTreeNode(self.m)
self.root = h
h.leaf = False
h.n = 0
h.children[0] = r
self.split_child(h, 0)
self.insert_in_node(h, value)
else:
self.insert_in_node(r, value)
def insert_in_node(self, start: BTreeNode, value):
start.load()
i = start.n
if start.leaf:
while i >= 1 and value < start.value[Literal(i-1)]:
start.value[Literal(i)] = start.value[Literal(i-1)]
i -= 1
start.value[Literal(i)].set(value)
start.n += 1
start.save()
else:
j = 0
while j < start.n and value > start.value[Literal(j)]:
j += 1
if start.children[j].n == 2 * self.m - 1:
self.split_child(start, j)
if value > start.value[Literal(j)]:
j += 1
self.insert_in_node(start.children[j], value)
def traversal(self, callback):
def traversal_recursive(node, callback):
i = 0
while i < node.n:
if not node.leaf:
traversal_recursive(node.children[i], callback)
callback(node.value[Literal(i)])
i += 1
if not node.leaf:
traversal_recursive(node.children[i], callback)
traversal_recursive(self.root, callback)
def walk(self):
def print_key(key):
print(key, end=" ")
self.traversal(print_key)
def height(self, start: BTreeNode = None):
if not start:
start = self.root
if start.leaf:
return 0
return 1 + self.height(start.children[0])
if __name__ == "__main__":
a = MemoryArray.create_array_from_file("data/seq3.txt")
tree = BTree(3)
for cell in a:
tree.insert(cell)
print(f"Height: {tree.height()}")
tree.walk()
s = tree.search(0)
print(f"\nKnoten mit 0: {str(s)}")

View File

@ -0,0 +1,29 @@
from utils.literal import Literal
from utils.memory_cell import MemoryCell
from utils.memory_array import MemoryArray
class BTreeNode(MemoryCell):
def __init__(self, m: int):
super().__init__()
self.m = m
self.n = 0
self.leaf = True
self.value = MemoryArray(Literal(2 * m - 1))
self.children = [None] * (2 * m)
self.loaded_count = 0
self.saved_count = 0
def reset_counters(self):
super().reset_counters()
self.loaded_count = 0
self.saved_count = 0
def load(self):
self.loaded_count += 1
def save(self):
self.saved_count += 1
def __str__(self):
return "(" + " ".join([str(self.value[Literal(i)]) for i in range(self.n)]) + ")"

View File

View File

@ -0,0 +1,60 @@
import math
import random
from utils.literal import Literal
from utils.memory_cell import MemoryCell
from utils.memory_array import MemoryArray
from utils.memory_manager import MemoryManager
from vorlesung.L07_hashtable.hashtable import HashTableOpenAddressing
#Goldener Schnitt
a = Literal((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 f(x: MemoryCell, i: Literal, m: Literal) -> Literal:
c1 = 1
c2 = 5
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:
with MemoryCell(h(x, m)) as base_hash, MemoryCell(int(i) * int(i)) as square:
if int(i) % 2 == 0: # gerades i: Vorwärtssondierung
with MemoryCell(base_hash + square) as position:
return position % m
else: # ungerades i: Rückwärtssondierung
with MemoryCell(base_hash - square) as position:
return position % m
def analyze_complexity(sizes):
"""
Analysiert die Komplexität
:param sizes: Eine Liste von Eingabegrößen für die Analyse.
"""
for size in sizes:
MemoryManager.purge() # Speicher zurücksetzen
ht = HashTableOpenAddressing(size, f)
random_array = MemoryArray.create_random_array(size, -100, 100)
for cell in random_array:
ht.insert(cell)
MemoryManager.reset()
cell = random.choice(random_array.cells)
ht.search(cell)
MemoryManager.save_stats(size)
MemoryManager.plot_stats(["cells", "compares"])
if __name__ == "__main__":
sizes = range(1, 1001, 10)
analyze_complexity(sizes)

View File

@ -0,0 +1,76 @@
from collections.abc import Callable
from utils.literal import Literal
from utils.memory_array import MemoryArray
from utils.memory_cell import MemoryCell
from utils.memory_range import mrange
UNUSED_MARK = "UNUSED"
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):
with MemoryCell(0) as i:
while i < self.m:
j = self.f(x, i, self.m)
if self.is_free(j):
self.table[j].set(x)
return True
i.set(i.succ())
return False
def search(self, x: MemoryCell):
with MemoryCell(0) as i:
while i < self.m:
j = self.f(x, i, self.m)
if self.is_unused(j):
return False
if self.table[j] == x:
return True
i.set(i.succ())
return False
def delete(self, x: MemoryCell):
with MemoryCell(0) as i:
while i < self.m:
j = self.f(x, i, self.m)
if self.is_unused(j):
return False
if self.table[j] == x:
self.table[j].value = DELETED_MARK
return True
i.set(i.succ())
return False
def __str__(self):
return str(self.table)
def alpha(self):
with MemoryCell(0) as i:
used = 0
while i < self.m:
used += 0 if self.is_free(i) else 1
i.set(i.succ())
return used / int(self.m)
def is_unused(self, i: Literal):
if self.table[i].value == UNUSED_MARK:
return True
return False
def is_deleted(self, i: Literal):
if self.table[i].value == DELETED_MARK:
return True
return False
def is_free(self, i: Literal):
return self.is_unused(i) or self.is_deleted(i)

View File

View File

@ -0,0 +1,45 @@
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))

View File

@ -0,0 +1,366 @@
from collections import deque
from typing import List
from enum import Enum
import graphviz
import math
import heapq
from datetime import datetime
from utils.project_dir import get_path
from utils.priority_queue import PriorityQueue
from vorlesung.L09_mst.disjoint import DisjointValue
class NodeColor(Enum):
"""Enumeration for node colors in a graph traversal."""
WHITE = 1 # WHITE: not visited
GRAY = 2 # GRAY: visited but not all neighbors visited
BLACK = 3 # BLACK: visited and all neighbors visited
class Vertex:
"""A vertex in a graph."""
def __init__(self, value):
self.value = value
def __str__(self):
return str(self.value)
def __repr__(self):
return f"Vertex({self.value})"
class Graph:
"""A graph."""
def insert_vertex(self, name: str):
raise NotImplementedError("Please implement this method in subclass")
def connect(self, name1: str, name2: str, weight: float = 1):
raise NotImplementedError("Please implement this method in subclass")
def all_vertices(self) -> List[Vertex]:
raise NotImplementedError("Please implement this method in subclass")
def get_vertex(self, name: str) -> Vertex:
raise NotImplementedError("Please implement this method in subclass")
def get_adjacent_vertices(self, name: str) -> List[Vertex]:
raise NotImplementedError("Please implement this method in subclass")
def get_adjacent_vertices_with_weight(self, name: str) -> List[tuple[Vertex, float]]:
raise NotImplementedError("Please implement this method in subclass")
def all_edges(self) -> List[tuple[str, str, float]]:
raise NotImplementedError("Please implement this method in subclass")
def bfs(self, start_name: str):
"""
Perform a breadth-first search starting at the given vertex.
:param start_name: the name of the vertex to start at
:return: a tuple of two dictionaries, the first mapping vertices to distances from the start vertex,
the second mapping vertices to their predecessors in the traversal tree
"""
color_map = {} # maps vertices to their color
distance_map = {} # maps vertices to their distance from the start vertex
predecessor_map = {} # maps vertices to their predecessor in the traversal tree
# Initialize the maps
for vertex in self.all_vertices():
color_map[vertex] = NodeColor.WHITE
distance_map[vertex] = None
predecessor_map[vertex] = None
# Start at the given vertex
start_node = self.get_vertex(start_name)
color_map[start_node] = NodeColor.GRAY
distance_map[start_node] = 0
# Initialize the queue with the start vertex
queue = deque()
queue.append(start_node)
# Process the queue
while len(queue) > 0:
vertex = queue.popleft()
for dest in self.get_adjacent_vertices(vertex.value):
if color_map[dest] == NodeColor.WHITE:
color_map[dest] = NodeColor.GRAY
distance_map[dest] = distance_map[vertex] + 1
predecessor_map[dest] = vertex
queue.append(dest)
color_map[vertex] = NodeColor.BLACK
# Return the distance and predecessor maps
return distance_map, predecessor_map
def dfs(self):
"""
Perform a depth-first search starting at the first vertex.
:return: a tuple of two dictionaries, the first mapping vertices to distances from the start vertex,
the second mapping vertices to their predecessors in the traversal tree
"""
color_map : dict[Vertex, NodeColor]= {}
enter_map : dict[Vertex, int] = {}
leave_map : dict[Vertex, int] = {}
predecessor_map : dict[Vertex, Vertex | None] = {}
white_vertices = set(self.all_vertices())
time_counter = 0
def dfs_visit(vertex):
nonlocal time_counter
color_map[vertex] = NodeColor.GRAY
white_vertices.remove(vertex)
time_counter += 1
enter_map[vertex] = time_counter
for dest in self.get_adjacent_vertices(vertex.value):
if color_map[dest] == NodeColor.WHITE:
predecessor_map[dest] = vertex
dfs_visit(dest)
color_map[vertex] = NodeColor.BLACK
time_counter += 1
leave_map[vertex] = time_counter
# Initialize the maps
for vertex in self.all_vertices():
color_map[vertex] = NodeColor.WHITE
predecessor_map[vertex] = None
while white_vertices:
v = white_vertices.pop()
dfs_visit(v)
return enter_map, leave_map, predecessor_map
def path(self, destination, map):
"""
Compute the path from the start vertex to the given destination vertex.
The map parameter is the predecessor map
"""
path = []
destination_node = self.get_vertex(destination)
while destination_node is not None:
path.insert(0, destination_node.value)
destination_node = map[destination_node]
return path
def graph(self, filename: str = "Graph"):
dot = graphviz.Digraph( name=filename,
node_attr={"fontname": "Arial"},
format="pdf" )
for vertex in self.all_vertices():
dot.node(str(id(vertex)), label=str(vertex.value))
for edge in self.all_edges():
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")
filename = f"{filename}_{timestamp}.gv"
filename = get_path(filename)
dot.render(filename)
def dijkstra(self, start_name: str) -> tuple[dict[Vertex, float], dict[Vertex, Vertex | None]]:
"""
Führt den Dijkstra-Algorithmus für kürzeste Pfade durch, implementiert mit Knotenfarben.
Args:
start_name: Name des Startknotens
Returns:
Ein Tupel aus zwei Dictionaries:
- distance_map: Abbildung von Knoten auf ihre kürzeste Distanz vom Startknoten
- predecessor_map: Abbildung von Knoten auf ihre Vorgänger im kürzesten Pfad
"""
def relax(vertex, dest, weight):
"""
Entspannt die Kante zwischen vertex und dest.
Aktualisiert die Distanz und den Vorgänger, wenn ein kürzerer Pfad gefunden wird.
"""
if distance_map[vertex] + weight < distance_map[dest]:
distance_map[dest] = distance_map[vertex] + weight
predecessor_map[dest] = vertex
queue.add_or_update(dest, distance_map[dest])
# Initialisierung der Maps
distance_map = {} # Speichert kürzeste Distanzen
predecessor_map = {} # Speichert Vorgänger
# Initialisiere alle Knoten
queue = PriorityQueue()
for vertex in self.all_vertices():
distance_map[vertex] = float('inf') # Initiale Distanz unendlich
predecessor_map[vertex] = None # Initialer Vorgänger None
queue.add_or_update(vertex, distance_map[vertex]) # Füge Knoten zur Prioritätswarteschlange hinzu
# Setze Startknoten
start_node = self.get_vertex(start_name)
distance_map[start_node] = 0
queue.add_or_update(start_node, distance_map[start_node])
while True:
entry = queue.pop()
if entry is None:
break
vertex = entry[0]
for dest, weight in self.get_adjacent_vertices_with_weight(vertex.value):
relax(vertex, dest, weight)
return distance_map, predecessor_map
def mst_prim(self, start_name: str = None):
""" Compute the minimum spanning tree of the graph using Prim's algorithm. """
distance_map = {} # maps vertices to their current distance from the spanning tree
parent_map = {} # maps vertices to their predecessor in the spanning tree
Vertex.__lt__ = lambda self, other: distance_map[self] < distance_map[other]
queue = []
if start_name is None:
start_name = self.all_vertices()[0].value
# Initialize the maps
for vertex in self.all_vertices():
distance_map[vertex] = 0 if vertex.value == start_name else math.inf
parent_map[vertex] = None
queue.append(vertex)
heapq.heapify(queue) # Convert the list into a heap
# Process the queue
cost = 0 # The cost of the minimum spanning tree
while len(queue) > 0:
vertex = heapq.heappop(queue)
cost += distance_map[vertex] # Add the cost of the edge to the minimum spanning tree
for (dest, w) in self.get_adjacent_vertices_with_weight(vertex.value):
if dest in queue and distance_map[dest] > w:
# Update the distance and parent maps
queue.remove(dest)
distance_map[dest] = w
parent_map[dest] = vertex
queue.append(dest) # Add the vertex back to the queue
heapq.heapify(queue) # Re-heapify the queue
# Return the distance and predecessor maps
return parent_map, cost
def mst_kruskal(self, start_name: str = None):
""" Compute the minimum spanning tree of the graph using Kruskal's algorithm. """
cost = 0
result = []
edges = self.all_edges()
# Create a disjoint set for each vertex
vertex_map = {v.value: DisjointValue(v) for v in self.all_vertices()}
# Sort the edges by weight
edges.sort(key=lambda edge: edge[2])
# Process the edges
for edge in edges:
start_name, end_name, weight = edge
# Check if the edge creates a cycle
if not vertex_map[start_name].same_set(vertex_map[end_name]):
result.append(edge)
vertex_map[start_name].union(vertex_map[end_name])
cost += weight
return result, cost
class AdjacencyListGraph(Graph):
"""A graph implemented as an adjacency list."""
def __init__(self):
self.adjacency_map = {} # maps vertex names to lists of adjacent vertices
self.vertex_map = {} # maps vertex names to vertices
def insert_vertex(self, name: str):
if name not in self.vertex_map:
self.vertex_map[name] = Vertex(name)
if name not in self.adjacency_map:
self.adjacency_map[name] = []
def connect(self, name1: str, name2: str, weight: float = 1):
adjacency_list = self.adjacency_map[name1]
dest = self.vertex_map[name2]
adjacency_list.append((dest, weight))
def all_vertices(self) -> List[Vertex]:
return list(self.vertex_map.values())
def get_vertex(self, name: str) -> Vertex:
return self.vertex_map[name]
def get_adjacent_vertices(self, name: str) -> List[Vertex]:
return list(map(lambda x: x[0], self.adjacency_map[name]))
def get_adjacent_vertices_with_weight(self, name: str) -> List[tuple[Vertex, float]]:
return self.adjacency_map[name]
def all_edges(self) -> List[tuple[str, str, float]]:
result = []
for name in self.adjacency_map:
for (dest, weight) in self.adjacency_map[name]:
result.append((name, dest.value, weight))
return result
class AdjacencyMatrixGraph(Graph):
"""A graph implemented as an adjacency matrix."""
def __init__(self):
self.index_map = {} # maps vertex names to indices
self.vertex_list = [] # list of vertices
self.adjacency_matrix = [] # adjacency matrix
def insert_vertex(self, name: str):
if name not in self.index_map:
self.index_map[name] = len(self.vertex_list)
self.vertex_list.append(Vertex(name))
for row in self.adjacency_matrix: # add a new column to each row
row.append(None)
self.adjacency_matrix.append([None] * len(self.vertex_list)) # add a new row
def connect(self, name1: str, name2: str, weight: float = 1):
index1 = self.index_map[name1]
index2 = self.index_map[name2]
self.adjacency_matrix[index1][index2] = weight
def all_vertices(self) -> List[Vertex]:
return self.vertex_list
def get_vertex(self, name: str) -> Vertex:
index = self.index_map[name]
return self.vertex_list[index]
def get_adjacent_vertices(self, name: str) -> List[Vertex]:
index = self.index_map[name]
result = []
for i in range(len(self.vertex_list)):
if self.adjacency_matrix[index][i] is not None:
name = self.vertex_list[i].value
result.append(self.get_vertex(name))
return result
def get_adjacent_vertices_with_weight(self, name: str) -> List[tuple[Vertex, float]]:
index = self.index_map[name]
result = []
for i in range(len(self.vertex_list)):
if self.adjacency_matrix[index][i] is not None:
name = self.vertex_list[i].value
result.append((self.get_vertex(name), self.adjacency_matrix[index][i]))
return result
def all_edges(self) -> List[tuple[str, str, float]]:
result = []
for i in range(len(self.vertex_list)):
for j in range(len(self.vertex_list)):
if self.adjacency_matrix[i][j] is not None:
result.append((self.vertex_list[i].value, self.vertex_list[j].value, self.adjacency_matrix[i][j]))
return result

View File

View File

@ -0,0 +1,18 @@
class DisjointValue():
def __init__(self, value):
self.value = value
self.parent = None
def canonical(self):
if self.parent:
return self.parent.canonical()
return self
def same_set(self, other):
return self.canonical() == other.canonical()
def union(self, other):
self.canonical().parent = other.canonical()