From 242b7fdc348fa40b174325a4c4f1f3aa07bc566d Mon Sep 17 00:00:00 2001 From: Oliver Hofmann Date: Thu, 13 Jun 2024 13:57:30 +0200 Subject: [PATCH] Lecture 7 --- SoSe24/lec03_sort_alg/algdat_heap.py | 52 +++++++++++++++++++++++ SoSe24/lec06_graph/disjoint.py | 18 ++++++++ SoSe24/lec06_graph/graph.py | 16 ++++++- SoSe24/lec06_graph/mst.py | 63 ++++++++++++++++++++++++---- 4 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 SoSe24/lec03_sort_alg/algdat_heap.py create mode 100644 SoSe24/lec06_graph/disjoint.py diff --git a/SoSe24/lec03_sort_alg/algdat_heap.py b/SoSe24/lec03_sort_alg/algdat_heap.py new file mode 100644 index 0000000..bda1652 --- /dev/null +++ b/SoSe24/lec03_sort_alg/algdat_heap.py @@ -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)] \ No newline at end of file diff --git a/SoSe24/lec06_graph/disjoint.py b/SoSe24/lec06_graph/disjoint.py new file mode 100644 index 0000000..293262e --- /dev/null +++ b/SoSe24/lec06_graph/disjoint.py @@ -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() diff --git a/SoSe24/lec06_graph/graph.py b/SoSe24/lec06_graph/graph.py index e652286..84683e2 100644 --- a/SoSe24/lec06_graph/graph.py +++ b/SoSe24/lec06_graph/graph.py @@ -39,6 +39,8 @@ class Graph: 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): """ @@ -124,6 +126,12 @@ class AdjacencyListGraph(Graph): 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 @@ -173,7 +181,13 @@ class AdjacencyMatrixGraph(Graph): 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 diff --git a/SoSe24/lec06_graph/mst.py b/SoSe24/lec06_graph/mst.py index 112b433..a432d0e 100644 --- a/SoSe24/lec06_graph/mst.py +++ b/SoSe24/lec06_graph/mst.py @@ -1,13 +1,16 @@ import math import re -from graph import Graph, AdjacencyListGraph, AdjacencyMatrixGraph, NodeColor, Vertex + +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 = {} - parent_map = {} + 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] @@ -20,26 +23,60 @@ def mst_prim(self, start_name: str = None): for vertex in self.all_vertices(): distance_map[vertex] = 0 if vertex.value == start_name else math.inf parent_map[vertex] = None - heapq.heappush(queue, vertex) + queue.append(vertex) + + heapq.heapify(queue) # Convert the list into a heap # Process the queue - cost = 0 + cost = 0 # The cost of the minimum spanning tree while len(queue) > 0: vertex = heapq.heappop(queue) - cost += distance_map[vertex] + 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 - heapq.heappush(queue, dest) + 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+)') @@ -59,8 +96,16 @@ if __name__ == "__main__": graph = AdjacencyMatrixGraph() read_elektro_into_graph(graph, "../../elektro.txt") + parents, cost = graph.mst_prim() - print(f"Kosten {cost}") + print(f"Kosten nach Prim: {cost}") for node, parent in parents.items(): if parent is not None: - print(f"{node} - {parent}") \ No newline at end of file + 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}") + +