diff --git a/DIRECTORY.md b/DIRECTORY.md index 777f5d34f..004545c9c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -213,6 +213,7 @@ * [Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) * [Bfs Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) * [Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) + * [Breadth First Search Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search_shortest_path.py) * [Check Bipartite Graph Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) * [Check Bipartite Graph Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) * [Depth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py new file mode 100644 index 000000000..514aed6d7 --- /dev/null +++ b/graphs/breadth_first_search_shortest_path.py @@ -0,0 +1,81 @@ +"""Breath First Search (BFS) can be used when finding the shortest path +from a given source node to a target node in an unweighted graph. +""" +graph = { + "A": ["B", "C", "E"], + "B": ["A", "D", "E"], + "C": ["A", "F", "G"], + "D": ["B"], + "E": ["A", "B", "D"], + "F": ["C"], + "G": ["C"], +} + +from typing import Dict + + +class Graph: + def __init__(self, graph: Dict[str, str], source_vertex: str) -> None: + """Graph is implemented as dictionary of adjancency lists. Also, + Source vertex have to be defined upon initialization. + """ + self.graph = graph + # mapping node to its parent in resulting breadth first tree + self.parent = {} + self.source_vertex = source_vertex + + def breath_first_search(self) -> None: + """This function is a helper for running breath first search on this graph. + >>> g = Graph(graph, "G") + >>> g.breath_first_search() + >>> g.parent + {'G': None, 'C': 'G', 'A': 'C', 'F': 'C', 'B': 'A', 'E': 'A', 'D': 'B'} + """ + visited = {self.source_vertex} + self.parent[self.source_vertex] = None + queue = [self.source_vertex] # first in first out queue + + while queue: + vertex = queue.pop(0) + for adjancent_vertex in self.graph[vertex]: + if adjancent_vertex not in visited: + visited.add(adjancent_vertex) + self.parent[adjancent_vertex] = vertex + queue.append(adjancent_vertex) + + def shortest_path(self, target_vertex: str) -> str: + """This shortest path function returns a string, describing the result: + 1.) No path is found. The string is a human readable message to indicate this. + 2.) The shortest path is found. The string is in the form `v1(->v2->v3->...->vn)`, + where v1 is the source vertex and vn is the target vertex, if it exists separately. + + >>> g = Graph(graph, "G") + >>> g.breath_first_search() + + Case 1 - No path is found. + >>> g.shortest_path("Foo") + 'No path from vertex:G to vertex:Foo' + + Case 2 - The path is found. + >>> g.shortest_path("D") + 'G->C->A->B->D' + >>> g.shortest_path("G") + 'G' + """ + if target_vertex == self.source_vertex: + return f"{self.source_vertex}" + elif not self.parent.get(target_vertex): + return f"No path from vertex:{self.source_vertex} to vertex:{target_vertex}" + else: + return self.shortest_path(self.parent[target_vertex]) + f"->{target_vertex}" + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + g = Graph(graph, "G") + g.breath_first_search() + print(g.shortest_path("D")) + print(g.shortest_path("G")) + print(g.shortest_path("Foo"))