Lecture 6

This commit is contained in:
Oliver Hofmann
2024-05-29 10:05:06 +02:00
parent 8ab7c0a835
commit e0cbf69759
7 changed files with 746 additions and 0 deletions
+181
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)