diff --git a/SoSe24/lec04_trees/avl_tree.py b/SoSe24/lec04_trees/avl_tree.py new file mode 100644 index 0000000..51a8d49 --- /dev/null +++ b/SoSe24/lec04_trees/avl_tree.py @@ -0,0 +1,111 @@ +from SoSe24.algodat.foundation import AlgoDatArray, AlgoDatValue, read_int_sequence +from SoSe24.lec04_trees.bin_tree import BinTree, BinTreeNode +from time import perf_counter as pfc + +class AVLTreeNode(BinTreeNode): + def __init__(self, value: AlgoDatValue): + super().__init__(value) + self.parent = None + self.balance = 0 + + 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) -> BinTreeNode: + 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 == 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) -> BinTreeNode: + 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 == 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) -> BinTreeNode: + self.right = self.right.right_rotate() + return self.left_rotate() + + def left_right_rotate(self) -> BinTreeNode: + self.left = self.left.left_rotate() + return self.right_rotate() + +class AVLTree(BinTree): + + def new_node(self, value: AlgoDatValue): + 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: AlgoDatValue): + node, parent = super().insert(value) + node.parent = parent + if parent: + self.balance(parent) + return node, parent + + def delete(self, value: AlgoDatValue): + node, parent = super().delete(value) + if node: + node.parent = parent + if parent: + self.balance(parent) + + + +if __name__ == "__main__": + z = read_int_sequence("../../seq0.txt") + print(z, len(z)) + start = pfc() + tree = AVLTree() + for i in z: + tree.insert(i) + tree.walk() + tree.tree_walk() + tree.levelwalk() + tree.graph_walk() + tree.delete(AlgoDatValue(46)) + tree.delete(AlgoDatValue(48)) + #tree.graph_walk() + print(f"Dauer: {pfc() - start:.4f}s") + AlgoDatValue.summary() \ No newline at end of file diff --git a/SoSe24/lec04_trees/avl_tree_plot.py b/SoSe24/lec04_trees/avl_tree_plot.py new file mode 100644 index 0000000..8027a52 --- /dev/null +++ b/SoSe24/lec04_trees/avl_tree_plot.py @@ -0,0 +1,29 @@ +from SoSe24.algodat.foundation import AlgoDatArray, AlgoDatValue, read_int_sequence, read_int_sequence_limited +import matplotlib +matplotlib.use('TkAgg') +import matplotlib.pyplot as plt +import avl_tree as avl + +if __name__ == "__main__": + filename = "../../seq3_sorted.txt" + #filename = "../../seq3.txt" + dummy = read_int_sequence(filename) + n = len(dummy) + step = n // 100 + + memory_values = [] + compare_values = [] + + for right_end in range(1, n, step): + AlgoDatValue.reset() + z = read_int_sequence_limited(filename, right_end) + tree = avl.AVLTree() + for i in z: + tree.insert(i) + memory_values.append(AlgoDatValue.memory) + compare_values.append(AlgoDatValue.compare) + + plt.plot(range(1, n, step), memory_values, 'b', label='Memory') + plt.plot(range(1, n, step), compare_values, 'r', label='Compare') + plt.legend() + plt.show() diff --git a/SoSe24/lec04_trees/b_tree.py b/SoSe24/lec04_trees/b_tree.py new file mode 100644 index 0000000..40c3caf --- /dev/null +++ b/SoSe24/lec04_trees/b_tree.py @@ -0,0 +1,133 @@ +from SoSe24.algodat.foundation import AlgoDatArray, AlgoDatValue, read_int_sequence +from time import perf_counter as pfc + + +class BTreeNode: + def __init__(self, m: int): + self.n = 0 + self.leaf = True + self.keys = AlgoDatArray(2 * m - 1) + self.children = [None] * (2 * m) + + def __str__(self): + return "(" + " ".join([str(self.keys[i]) for i in range(self.n)]) + ")" + +class BTree: + def __init__(self, m: int): + self.m = m + self.root = BTreeNode(m) + + def search(self, key: AlgoDatValue, start: BTreeNode = None) -> BTreeNode: + if not start: + start = self.root + i = 0 + while i < start.n and key > start.keys[i]: + i += 1 + if i < start.n and key == start.keys[i]: + return start + if start.leaf: + return None + return self.search(key, start.children[i]) + + def split_child(self, parent: BTreeNode, i: int): + child = parent.children[i] + h = BTreeNode(self.m) + h.leaf = child.leaf + h.n = self.m - 1 + for j in range(self.m - 1): + h.keys[j] = child.keys[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 + for j in range(parent.n, i, -1): + parent.children[j + 1] = parent.children[j] + parent.keys[j] = parent.keys[j - 1] + parent.children[i + 1] = h + parent.keys[i] = child.keys[self.m - 1] + parent.n += 1 + + def insert(self, k: AlgoDatValue): + 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, k) + else: + self.insert_in_node(r, k) + + def insert_in_node(self, start: BTreeNode, k: AlgoDatValue): + i = start.n + if start.leaf: + while i >= 1 and k < start.keys[i-1]: + start.keys[i] = start.keys[i-1] + i -= 1 + start.keys[i] = k + start.n += 1 + else: + j = 0 + while j < start.n and k > start.keys[j]: + j += 1 + if start.children[j].n == 2 * self.m - 1: + self.split_child(start, j) + if k > start.keys[j]: + j += 1 + self.insert_in_node(start.children[j], k) + + def walk(self, start: BTreeNode = None): + if not start: + start = self.root + i = 0 + while i < start.n: + if not start.leaf: + self.walk(start.children[i]) + print(start.keys[i], end=" ") + i += 1 + if not start.leaf: + self.walk(start.children[i]) + + def height(self, start: BTreeNode = None): + if not start: + start = self.root + if start.leaf: + return 0 + return 1 + self.height(start.children[0]) + + def graph_walk(self): + queue = [ self.root ] + with open("../../btree.gv", "w") as file: + file.write("digraph BTree {\n") + file.write(" node [fontname=\"Arial\"];\n") + while queue: + current = queue.pop(0) + p = str(current) + file.write(f'"{p}"; \n') + i = 0 + while i <= current.n: + if not current.leaf: + queue.append(current.children[i]) + c = str(current.children[i]) + file.write(f'"{p}" -> "{c}";\n') + i += 1 + file.write("}") + + +if __name__ == "__main__": + z = read_int_sequence("../../seq2.txt") + start = pfc() + tree = BTree(3) + for i in z: + tree.insert(i) + print(f"Height: {tree.height()}") + tree.walk() + tree.graph_walk() + s = tree.search(AlgoDatValue(0)) + print(f"\nKnoten mit 0: {str(s)}") + print(f"Dauer: {pfc() - start:.4f}s") + AlgoDatValue.summary() \ No newline at end of file diff --git a/SoSe24/lec04_trees/bin_tree.py b/SoSe24/lec04_trees/bin_tree.py new file mode 100644 index 0000000..589e04a --- /dev/null +++ b/SoSe24/lec04_trees/bin_tree.py @@ -0,0 +1,171 @@ +from SoSe24.algodat.foundation import AlgoDatArray, AlgoDatValue, read_int_sequence +from time import perf_counter as pfc + +class BinTreeNode: + def __init__(self, value: AlgoDatValue): + self.value = value + self.left = None + self.right = None + + def __str__(self): + return f"{self.value}" + + def height(self) -> int: + left_height = self.left.height() if self.left else 0 + right_height = self.right.height() if self.right else 0 + return 1 + max(left_height, right_height) + + + +class BinTree: + def __init__(self): + self.root = None + + def new_node(self, value: AlgoDatValue) -> BinTreeNode: + return BinTreeNode(value) + + def insert(self, value: AlgoDatValue) -> (BinTreeNode, BinTreeNode): + if not self.root: + self.root = self.new_node(value) + return self.root, None + + current = self.root + while True: + if value < current.value: + if current.left: + current = current.left + else: + current.left = self.new_node(value) + return current.left, current + elif value >= current.value: + if current.right: + current = current.right + else: + current.right = self.new_node(value) + return current.right, current + else: + return None, None + + def search(self, value: AlgoDatValue) -> BinTreeNode: + current = self.root + while current: + if value < current.value: + current = current.left + elif value > current.value: + current = current.right + else: + return current + return None + + def delete(self, value: AlgoDatValue): + parent = None + current = self.root + while current: + if value < current.value: + parent = current + current = current.left + elif value >= current.value: + parent = current + current = current.right + else: + break + else: + return + + if current.left and current.right: + parent = current + successor = current.right + while successor.left: + parent = successor + successor = successor.left + current.value.value = successor.value.value + current = successor + + if current.left: + child = current.left + else: + child = current.right + + if not parent: + self.root = child + return child, None + elif parent.left == current: + parent.left = child + return child, parent + else: + parent.right = child + return child, parent + + def walk(self): # in-order + print("[ ", end="") + self.walk_recursive(self.root, 0, 0) + print(" ]") + + + def graph_walk(self): + self.leaf_counter = 0 + with open("../../graph.gv", "w") as file: + file.write("digraph BST {\n") + file.write(" node [fontname=\"Arial\"];\n") + self.graph_walk_recursive(self.root, file) + file.write("}") + + def graph_walk_recursive(self, current, file): + if current is not None: + if current.left: + file.write(f"{current.value} -> {current.left.value}; \n") + self.graph_walk_recursive(current.left, file) + else: + file.write(f"left{self.leaf_counter} [shape=point]; \n") + file.write(f"{current.value} -> left{self.leaf_counter}; \n") + self.leaf_counter += 1 + if current.right: + file.write(f"{current.value} -> {current.right.value}; \n") + self.graph_walk_recursive(current.right, file) + else: + file.write(f"right{self.leaf_counter} [shape=point]; \n") + file.write(f"{current.value} -> right{self.leaf_counter}; \n") + self.leaf_counter += 1 + + + def tree_walk(self): + self.walk_recursive(self.root, 0, 1) + + def walk_recursive(self, node: BinTreeNode, level = 0, increase = 1): + if node: + if increase >= 1: + end = "\n" + else: + end = " " + self.walk_recursive(node.left, level+increase, increase) + print(" "*level*3 + str(node.value), end=end) + self.walk_recursive(node.right, level+increase, increase) + + def levelwalk(self): + if self.root is None: + return + queue = [self.root] + while queue: + current = queue.pop(0) + print(current.value, end=" ") + if current.left: + queue.append(current.left) + if current.right: + queue.append(current.right) + print() + + +if __name__ == "__main__": + z = read_int_sequence("../../seq0.txt") + print(z, len(z)) + start = pfc() + tree = BinTree() + for i in z: + tree.insert(i) + tree.walk() + tree.tree_walk() + tree.levelwalk() + tree.delete(AlgoDatValue(46)) + tree.graph_walk() + print(f"Dauer: {pfc() - start:.4f}s") + AlgoDatValue.summary() \ No newline at end of file diff --git a/SoSe24/lec04_trees/bin_tree_plot.py b/SoSe24/lec04_trees/bin_tree_plot.py new file mode 100644 index 0000000..300de7d --- /dev/null +++ b/SoSe24/lec04_trees/bin_tree_plot.py @@ -0,0 +1,30 @@ +from SoSe24.algodat.foundation import AlgoDatArray, AlgoDatValue, read_int_sequence, read_int_sequence_limited +import matplotlib +matplotlib.use('TkAgg') +import matplotlib.pyplot as plt +import bin_tree as bt + +if __name__ == "__main__": + filename = "../../seq3_sorted.txt" + #filename = "../../seq3.txt" + dummy = read_int_sequence(filename) + n = len(dummy) + step = n // 100 + + memory_values = [] + compare_values = [] + + for right_end in range(1, n, step): + AlgoDatValue.reset() + z = read_int_sequence_limited(filename, right_end) + tree = bt.BinTree() + for i in z: + tree.insert(i) + memory_values.append(AlgoDatValue.memory) + compare_values.append(AlgoDatValue.compare) + print(right_end, AlgoDatValue.compare) + + plt.plot(range(1, n, step), memory_values, 'b', label='Memory') + plt.plot(range(1, n, step), compare_values, 'r', label='Compare') + plt.legend() + plt.show() diff --git a/SoSe24/lec05_hash/hash_table.py b/SoSe24/lec05_hash/hash_table.py new file mode 100644 index 0000000..d40c803 --- /dev/null +++ b/SoSe24/lec05_hash/hash_table.py @@ -0,0 +1,91 @@ +from SoSe24.algodat.foundation import AlgoDatArray, AlgoDatValue, read_int_sequence, MinusInf +from time import perf_counter as pfc + +#Goldener Schnitt +import math +a = (math.sqrt(5) - 1) / 2 + +def h(x, m): + return int(x*a - int(x*a) * m) + +def f(x, i, m): + return (h(x, m) + i + 14*i*i) % m + +def f1(x, i, m): + if i % 2 == 0: + return (h(x, m) + i*i) % m + return ((h(x, m) - i*i) % m + m) % m + +class HashTable: + def __init__(self, m, h, f=None): + self.m = m + self.h = h + self.f = f + self.table = AlgoDatArray(m) + + def insert(self, x): + i = 0 + while i < self.m: + j = self.f(x.value, i, self.m) + if self.is_free(j): + self.table[j].value = x.value + return True + i += 1 + return False + + def search(self, x): + i = 0 + while i < self.m: + j = f(x, i, self.m) + if self.table[j] == x: + return True + if self.table[j] == None: + return False + i += 1 + return False + + def delete(self, x): + i = 0 + while i < self.m: + j = f(x, i, self.m) + if self.table[j].value == x: + self.table[j].value = "DELETED" + return True + if self.table[j].value is None: + return False + i += 1 + return False + + def __str__(self): + return str(self.table) + + def alpha(self): + i=0 + used = 0 + while i < self.m: + used += 0 if self.is_free(i) else 1 + i += 1 + return used / self.m + + def is_free(self, i): + if self.table[i] == None: + return True + if self.table[i] == "DELETED": + return True + return False + +if __name__ == "__main__": + z = read_int_sequence("../../seq1.txt") + start = pfc() + hash = HashTable(31, h, f) + for i in z: + hash.insert(i) + print(hash) + print(f"Alpha: {hash.alpha()}") + hash.delete(34) + hash.search(47) + hash.search(243) + print(hash) + print(f"Alpha: {hash.alpha()}") + print(f"Dauer: {pfc() - start:.4f}s") + AlgoDatValue.summary() \ No newline at end of file diff --git a/SoSe24/lec06_graph/bfs.py b/SoSe24/lec06_graph/bfs.py new file mode 100644 index 0000000..90771c3 --- /dev/null +++ b/SoSe24/lec06_graph/bfs.py @@ -0,0 +1,181 @@ +from collections import deque +from typing import List +import re +from enum import Enum + +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 + +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): + 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 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 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 + + + +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): + adjacency_list = self.adjacency_map[name1] + dest = self.vertex_map[name2] + adjacency_list.append(dest) + + 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 self.adjacency_map[name] + +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(0) + self.adjacency_matrix.append([0] * len(self.vertex_list)) # add a new row + + def connect(self, name1: str, name2: str): + index1 = self.index_map[name1] + index2 = self.index_map[name2] + self.adjacency_matrix[index1][index2] = 1 + + 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] == 1: + name = self.vertex_list[i].value + result.append(self.get_vertex(name)) + return result + +def read_cave_into_graph(graph: Graph, filename: str): + """Read a cave description from a file and insert it into the given graph.""" + 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) + + +if __name__ == "__main__": + graph = AdjacencyListGraph() + #graph = AdjacencyMatrixGraph() + read_cave_into_graph(graph, "../../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)