diff --git a/praktika/05_avl_baum/gv_avl_tree.py b/praktika/05_avl_baum/gv_avl_tree.py new file mode 100644 index 0000000..2b66847 --- /dev/null +++ b/praktika/05_avl_baum/gv_avl_tree.py @@ -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() + diff --git a/praktika/05_avl_baum/gv_binary_tree.py b/praktika/05_avl_baum/gv_binary_tree.py new file mode 100644 index 0000000..b0afb58 --- /dev/null +++ b/praktika/05_avl_baum/gv_binary_tree.py @@ -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() + diff --git a/requirements.txt b/requirements.txt index 7f12edc..ce33b86 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ matplotlib numpy pygame +graphviz diff --git a/vorlesung/L05_binaere_baeume/analyze_avl_tree.py b/vorlesung/L05_binaere_baeume/analyze_avl_tree.py new file mode 100644 index 0000000..96b74ca --- /dev/null +++ b/vorlesung/L05_binaere_baeume/analyze_avl_tree.py @@ -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) \ No newline at end of file diff --git a/vorlesung/L05_binaere_baeume/avl_tree.py b/vorlesung/L05_binaere_baeume/avl_tree.py new file mode 100644 index 0000000..e4446d3 --- /dev/null +++ b/vorlesung/L05_binaere_baeume/avl_tree.py @@ -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) diff --git a/vorlesung/L05_binaere_baeume/avl_tree_game.py b/vorlesung/L05_binaere_baeume/avl_tree_game.py new file mode 100644 index 0000000..d726bb7 --- /dev/null +++ b/vorlesung/L05_binaere_baeume/avl_tree_game.py @@ -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() + diff --git a/vorlesung/L05_binaere_baeume/avl_tree_node.py b/vorlesung/L05_binaere_baeume/avl_tree_node.py new file mode 100644 index 0000000..ff4ccb9 --- /dev/null +++ b/vorlesung/L05_binaere_baeume/avl_tree_node.py @@ -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() + diff --git a/vorlesung/L05_binaere_baeume/bin_tree.py b/vorlesung/L05_binaere_baeume/bin_tree.py index 49e12a8..855a1fb 100644 --- a/vorlesung/L05_binaere_baeume/bin_tree.py +++ b/vorlesung/L05_binaere_baeume/bin_tree.py @@ -3,6 +3,7 @@ from utils.memory_manager import MemoryManager from utils.memory_array import MemoryArray from utils.project_dir import get_path from datetime import datetime +import graphviz class BinaryTree: @@ -69,7 +70,7 @@ class BinaryTree: break else: # Wert nicht gefunden - return + return None, None return self.delete_node(current, parent) def delete_node(self, current, parent): @@ -144,37 +145,35 @@ class BinaryTree: line = 0 tree_structure_traversal_recursive(callback, self.root, 0) + def graph_filename(self): + return "BinaryTree" def graph_traversal(self): - def define_node(node, level, line): - nonlocal file + nonlocal dot if node is not None: - file.write(node.gv_rep(level, line)) + node.graphviz_rep(level, line, dot) def graph_traversal_recursive(current): - nonlocal file + nonlocal dot if current is not None: 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) 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) - timestamp = datetime.now().strftime("%Y%m%d_%H:%M:%S") - filename = f"graph_{timestamp}.gv" + dot = graphviz.Digraph( name="BinaryTree", + engine="neato", + node_attr={"shape": "circle", "fontname": "Arial"}, + format="pdf" ) + self.tree_structure_traversal(define_node) + graph_traversal_recursive(self.root) + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"{self.graph_filename()}_{timestamp}.gv" filename = get_path(filename) - with open(filename, "w") as file: - file.write("digraph BST {\n") - file.write("layout=neato;\n") - file.write("node [shape=circle, fontname=\"Arial\"];\n") - self.tree_structure_traversal(define_node) - graph_traversal_recursive(self.root) - file.write("}") - - - + dot.render(filename) if __name__ == "__main__": tree = BinaryTree() diff --git a/vorlesung/L05_binaere_baeume/bin_tree_game.py b/vorlesung/L05_binaere_baeume/bin_tree_game.py new file mode 100644 index 0000000..3aef208 --- /dev/null +++ b/vorlesung/L05_binaere_baeume/bin_tree_game.py @@ -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() + diff --git a/vorlesung/L05_binaere_baeume/bin_tree_node.py b/vorlesung/L05_binaere_baeume/bin_tree_node.py index 148b6b1..d46dcff 100644 --- a/vorlesung/L05_binaere_baeume/bin_tree_node.py +++ b/vorlesung/L05_binaere_baeume/bin_tree_node.py @@ -18,7 +18,5 @@ class BinaryTreeNode(MemoryCell): def __str__(self): return str(self.value) - def gv_rep(self, row, col): - """Returns the graphviz representation of the node.""" - return f"{id(self)} [label=\"{self.value}\", pos=\"{col},{-row}!\"];\n" - + def graphviz_rep(self, row, col, dot): + dot.node(str(id(self)), label=str(self.value), pos=f"{col},{-row}!") \ No newline at end of file diff --git a/vorlesung/L06_b_baeume/analyze_b_tree.py b/vorlesung/L06_b_baeume/analyze_b_tree.py new file mode 100644 index 0000000..e142ee8 --- /dev/null +++ b/vorlesung/L06_b_baeume/analyze_b_tree.py @@ -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) \ No newline at end of file diff --git a/vorlesung/L06_b_baeume/b_tree.py b/vorlesung/L06_b_baeume/b_tree.py new file mode 100644 index 0000000..7729930 --- /dev/null +++ b/vorlesung/L06_b_baeume/b_tree.py @@ -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)}") diff --git a/vorlesung/L06_b_baeume/b_tree_node.py b/vorlesung/L06_b_baeume/b_tree_node.py new file mode 100644 index 0000000..20b8bb5 --- /dev/null +++ b/vorlesung/L06_b_baeume/b_tree_node.py @@ -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)]) + ")" \ No newline at end of file