Compare commits

..
6 Commits
Author SHA1 Message Date
Oliver Hofmann 242b7fdc34 Lecture 7 2024-06-13 13:57:30 +02:00
hofmannol 00466b6ad7 lecture 8 2024-06-11 16:56:00 +02:00
Oliver Hofmann 4bad896096 Lecture 7 2024-06-06 13:32:50 +02:00
Oliver Hofmann 82a6d35942 Lecture 6 2024-06-05 16:16:33 +02:00
Oliver Hofmann 50cf0b9b52 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	SoSe24/lec04_trees/avl_tree.py
#	SoSe24/lec04_trees/b_tree.py
#	SoSe24/lec05_hash/hash_table.py
2024-05-29 10:05:24 +02:00
Oliver Hofmann e0cbf69759 Lecture 6 2024-05-29 10:05:06 +02:00
9 changed files with 567 additions and 24 deletions

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
from SoSe24.algodat.foundation import AlgoDatArray
class AlgoDatHeap():
def __init__(self, values: AlgoDatArray):
self.heap = values
self.size = len(values)
def left_child(self, i):
return 2*i + 1
def right_child(self, i):
return 2*i + 2
def parent(self, i):
return (i-1)//2
def max_heapify(self, i):
l = self.left_child(i)
r = self.right_child(i)
largest = i
if l <= self.size and self.heap[l-1] > self.heap[i-1]:
largest = l
if r <= self.size and self.heap[r-1] > self.heap[largest-1]:
largest = r
if largest != i:
self.heap[i-1], self.heap[largest-1] = self.heap[largest-1], self.heap[i-1]
self.max_heapify(largest)
def min_heapify(self, i):
l = self.left_child(i)
r = self.right_child(i)
smallest = i
if l <= self.size and self.heap[l-1] < self.heap[i-1]:
smallest = l
if r <= self.size and self.heap[r-1] < self.heap[smallest-1]:
smallest = r
if smallest != i:
self.heap[i-1], self.heap[smallest-1] = self.heap[smallest-1], self.heap[i-1]
self.min_heapify(smallest)
def build_max_heap(self):
for i in range(self.size//2, 0, -1):
self.max_heapify(i)
def build_min_heap(self):
for i in range(self.size//2, 0, -1):
self.min_heapify(i)
def as_list(self):
return [self.heap[i].value for i in range(self.size)]
-1
View File
@@ -2,7 +2,6 @@ from SoSe24.algodat.foundation import AlgoDatArray, AlgoDatValue, read_int_seque
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)
-1
View File
@@ -12,7 +12,6 @@ class BTreeNode:
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
+12 -22
View File
@@ -4,45 +4,36 @@ from time import perf_counter as pfc
#Goldener Schnitt
import math
a = (math.sqrt(5) - 1) / 2
explore_steps = 0
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, hash_function, exploratory_function=None):
def __init__(self, m, h, f=None):
self.m = m
self.h = hash_function
self.f = exploratory_function
self.n = 0
self.h = h
self.f = f
self.table = AlgoDatArray(m)
def insert(self, x):
global explore_steps
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
self.n += 1
return True
i += 1
explore_steps += 1
return False
def search(self, x):
global explore_steps
i = 0
while i < self.m:
j = f(x, i, self.m)
@@ -51,29 +42,30 @@ class HashTable:
if self.table[j] == None:
return False
i += 1
explore_steps += 1
return False
def delete(self, x):
global explore_steps
i = 0
while i < self.m:
j = f(x, i, self.m)
if self.table[j].value == x:
self.table[j].value = "DELETED"
self.n -= 1
return True
if self.table[j].value is None:
return False
i += 1
explore_steps += 1
return False
def __str__(self):
return str(self.table)
def alpha(self):
return self.n / self.m
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:
@@ -82,20 +74,18 @@ class HashTable:
return True
return False
if __name__ == "__main__":
z = read_int_sequence("../../seq1.txt")
start = pfc()
hash = HashTable(31, h, f)
for algodat_value in z:
hash.insert(algodat_value)
for i in z:
hash.insert(i)
print(hash)
print(f"Alpha: {hash.alpha()}")
hash.delete(11)
hash.delete(34)
hash.search(47)
hash.search(243)
print(hash)
print(f"Alpha: {hash.alpha()}")
print(f"Dauer: {pfc() - start:.4f}s")
print(f"Sondierungsschritte: {explore_steps}")
AlgoDatValue.summary()
+141
View File
@@ -0,0 +1,141 @@
import math
from typing import Callable
import re
from graph import Graph, AdjacencyListGraph, AdjacencyMatrixGraph, NodeColor, Vertex
import heapq
def a_star(self, start_name: str, end_name: str, heuristic: Callable[[Vertex], float]):
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
def cost(vertex: Vertex) -> float:
"""Compute the cost of the path to the given vertex."""
if distance_map[vertex] is None:
return math.inf
return distance_map[vertex] + heuristic(vertex)
Vertex.__lt__ = lambda self, other: cost(self) < cost(other)
# 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 = [] # Use a list instead of a deque, because we need to sort it with
heapq.heappush(queue, start_node)
# Process the queue
while len(queue) > 0:
vertex = heapq.heappop(queue)
if color_map[vertex] == NodeColor.BLACK:
# Skip already processed vertices (possibly with a higher cost)
continue
if vertex.value == end_name:
# Return the distance and predecessor maps
return distance_map, predecessor_map
for dest, weight in self.get_adjacent_vertices_with_weight(vertex.value):
if color_map[dest] == NodeColor.BLACK:
continue
f = distance_map[vertex] + weight + heuristic(dest)
if color_map[dest] == NodeColor.GRAY and f > cost(dest):
continue
predecessor_map[dest] = vertex
distance_map[dest] = distance_map[vertex] + weight
heapq.heappush(queue, dest)
if color_map[dest] == NodeColor.WHITE:
color_map[dest] = NodeColor.GRAY
color_map[vertex] = NodeColor.BLACK
# Return the distance and predecessor maps if no path was found
return None, None
# Add the a_star method to the Graph classes
AdjacencyListGraph.a_star = a_star
AdjacencyMatrixGraph.a_star = a_star
if __name__ == "__main__":
def read_labyrinth_into_graph(graph: Graph, filename: str):
"""Read a labyrinth from a file into a graph. The file format is a grid of characters:"""
start = None
end = None
with open(filename, "r") as file:
nodes = []
lines = file.readlines()
for y, line in enumerate(lines):
for x, char in enumerate(line):
if char in ' AS':
name = pos_to_nodename((x, y))
graph.insert_vertex(name)
nodes.append((x, y))
if char == 'A':
end = (x, y)
if char == 'S':
start = (x, y)
for x, y in nodes:
name1 = f"x{x}y{y}"
for neighbor in [(x - 1, y), (x, y - 1), (x, y + 1), (x + 1, y)]:
if neighbor in nodes:
name_neighbor = pos_to_nodename(neighbor)
graph.connect(name1, name_neighbor, 1)
return start, end, lines
def nodename_to_pos(nodename):
"""Convert a node name to a position (x, y)."""
m = re.match(r"(^x(\d*)y(\d*))", nodename)
if m:
return (int(m.group(2)), int(m.group(3)))
return None
def pos_to_nodename(pos):
"""Convert a position (x, y) to a node name."""
x, y = pos
return f"x{x}y{y}"
def get_heuristic(end) -> Callable[[Vertex], float]:
"""Return a heuristic function for the given end position."""
def heuristic(v: Vertex) -> float:
x, y = nodename_to_pos(v.value)
x1, y1 = end
# Euclidean distance
return math.sqrt(abs(x - x1)**2 + abs(y - y1)**2)
return heuristic
def update_lines(lines, distance_map, path):
"""Update the lines with the path found by the A* algorithm."""
def replace_at(x, y, replacement):
if lines[y][x] not in " .":
return
lines[y] = lines[y][:x] + replacement + lines[y][x+1:]
for node in distance_map.keys():
if distance_map[node] is not None:
x, y = nodename_to_pos(node.value)
replace_at(x, y, ".")
for node in path:
x, y = nodename_to_pos(node)
replace_at(x, y, "*")
return lines
graph = AdjacencyListGraph()
#graph = AdjacencyMatrixGraph()
start, end, lines = read_labyrinth_into_graph(graph, "../../labyrinth.txt")
distance_map, predecessor_map = graph.a_star(pos_to_nodename(start), pos_to_nodename(end), get_heuristic(end))
endname = pos_to_nodename(end)
lines = update_lines(lines, distance_map, graph.path(endname, predecessor_map))
for line in lines:
print(line, end="")
+18
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()
+225
View File
@@ -0,0 +1,225 @@
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
def __repr__(self):
return str(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 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, 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
if __name__ == "__main__":
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)
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)
+111
View File
@@ -0,0 +1,111 @@
import math
import re
from SoSe24.lec06_graph.disjoint import DisjointValue
from graph import Graph, AdjacencyListGraph, AdjacencyMatrixGraph, Vertex
import heapq
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
AdjacencyListGraph.mst_prim = mst_prim
AdjacencyMatrixGraph.mst_prim = mst_prim
AdjacencyListGraph.mst_kruskal = mst_kruskal
AdjacencyMatrixGraph.mst_kruskal = mst_kruskal
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, "../../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}")
+8
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