Browse Source

Lecture 6

master
Oliver Hofmann 1 month ago
parent
commit
e0cbf69759

+ 111
- 0
SoSe24/lec04_trees/avl_tree.py View File

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

+ 29
- 0
SoSe24/lec04_trees/avl_tree_plot.py View File

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

+ 133
- 0
SoSe24/lec04_trees/b_tree.py View File

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

+ 171
- 0
SoSe24/lec04_trees/bin_tree.py View File

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

+ 30
- 0
SoSe24/lec04_trees/bin_tree_plot.py View File

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

+ 91
- 0
SoSe24/lec05_hash/hash_table.py View File

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

+ 181
- 0
SoSe24/lec06_graph/bfs.py View File

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

Loading…
Cancel
Save