mirror of
https://github.com/TheAlgorithms/Python.git
synced 2024-12-18 01:00:15 +00:00
fa364dfd27
Updated the code to track visited Nodes with Set data structure instead of Lists to bring down the lookup time in visited from O(N) to O(1) as doing O(N) lookup each time in the visited List will become significantly slow when the graph grows
107 lines
3.3 KiB
Python
107 lines
3.3 KiB
Python
"""Breadth-first search shortest path implementations.
|
|
doctest:
|
|
python -m doctest -v bfs_shortest_path.py
|
|
Manual test:
|
|
python bfs_shortest_path.py
|
|
"""
|
|
graph = {
|
|
"A": ["B", "C", "E"],
|
|
"B": ["A", "D", "E"],
|
|
"C": ["A", "F", "G"],
|
|
"D": ["B"],
|
|
"E": ["A", "B", "D"],
|
|
"F": ["C"],
|
|
"G": ["C"],
|
|
}
|
|
|
|
|
|
def bfs_shortest_path(graph: dict, start, goal) -> str:
|
|
"""Find shortest path between `start` and `goal` nodes.
|
|
Args:
|
|
graph (dict): node/list of neighboring nodes key/value pairs.
|
|
start: start node.
|
|
goal: target node.
|
|
Returns:
|
|
Shortest path between `start` and `goal` nodes as a string of nodes.
|
|
'Not found' string if no path found.
|
|
Example:
|
|
>>> bfs_shortest_path(graph, "G", "D")
|
|
['G', 'C', 'A', 'B', 'D']
|
|
"""
|
|
# keep track of explored nodes
|
|
explored = set()
|
|
# keep track of all the paths to be checked
|
|
queue = [[start]]
|
|
|
|
# return path if start is goal
|
|
if start == goal:
|
|
return "That was easy! Start = goal"
|
|
|
|
# keeps looping until all possible paths have been checked
|
|
while queue:
|
|
# pop the first path from the queue
|
|
path = queue.pop(0)
|
|
# get the last node from the path
|
|
node = path[-1]
|
|
if node not in explored:
|
|
neighbours = graph[node]
|
|
# go through all neighbour nodes, construct a new path and
|
|
# push it into the queue
|
|
for neighbour in neighbours:
|
|
new_path = list(path)
|
|
new_path.append(neighbour)
|
|
queue.append(new_path)
|
|
# return path if neighbour is goal
|
|
if neighbour == goal:
|
|
return new_path
|
|
|
|
# mark node as explored
|
|
explored.add(node)
|
|
|
|
# in case there's no path between the 2 nodes
|
|
return "So sorry, but a connecting path doesn't exist :("
|
|
|
|
|
|
def bfs_shortest_path_distance(graph: dict, start, target) -> int:
|
|
"""Find shortest path distance between `start` and `target` nodes.
|
|
Args:
|
|
graph: node/list of neighboring nodes key/value pairs.
|
|
start: node to start search from.
|
|
target: node to search for.
|
|
Returns:
|
|
Number of edges in shortest path between `start` and `target` nodes.
|
|
-1 if no path exists.
|
|
Example:
|
|
>>> bfs_shortest_path_distance(graph, "G", "D")
|
|
4
|
|
>>> bfs_shortest_path_distance(graph, "A", "A")
|
|
0
|
|
>>> bfs_shortest_path_distance(graph, "A", "H")
|
|
-1
|
|
"""
|
|
if not graph or start not in graph or target not in graph:
|
|
return -1
|
|
if start == target:
|
|
return 0
|
|
queue = [start]
|
|
visited = set(start)
|
|
# Keep tab on distances from `start` node.
|
|
dist = {start: 0, target: -1}
|
|
while queue:
|
|
node = queue.pop(0)
|
|
if node == target:
|
|
dist[target] = (
|
|
dist[node] if dist[target] == -1 else min(dist[target], dist[node])
|
|
)
|
|
for adjacent in graph[node]:
|
|
if adjacent not in visited:
|
|
visited.add(adjacent)
|
|
queue.append(adjacent)
|
|
dist[adjacent] = dist[node] + 1
|
|
return dist[target]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print(bfs_shortest_path(graph, "G", "D")) # returns ['G', 'C', 'A', 'B', 'D']
|
|
print(bfs_shortest_path_distance(graph, "G", "D")) # returns 4
|