|
| 1 | +from plumbum import cli |
| 2 | +from graph import Graph, inf |
| 3 | +from collections import deque |
| 4 | +import pprint |
| 5 | + |
| 6 | +''' |
| 7 | +https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/ |
| 8 | +https://dev.to/mxl/dijkstras-algorithm-in-python-algorithms-for-beginners-dkc |
| 9 | +http://interactivepython.org/courselib/static/pythonds/Graphs/DijkstrasAlgorithm.html |
| 10 | +''' |
| 11 | +class DijkstrasAlgorithm(cli.Application): |
| 12 | + |
| 13 | + # a - 2 -> b - 7 -> | |
| 14 | + # | | 10 | |
| 15 | + # | - 9 -> c | |
| 16 | + # | | 2 | |
| 17 | + # | - 14 -> d <- - - | |
| 18 | + |
| 19 | + _graph = Graph([ |
| 20 | + ("a", "b", 2), ("a", "c", 9), ("b", "c", 10), |
| 21 | + ("b", "d", 7), ("a", "d", 14), ("c", "d", 2) |
| 22 | + ]) |
| 23 | + |
| 24 | + def main(self): |
| 25 | + if list is None or len(self._graph.vertices) is 0: |
| 26 | + print("List should have at least one element") |
| 27 | + return 1 |
| 28 | + else: |
| 29 | + dijkstras = self.run_dijkstras(self._graph, "a", "d") |
| 30 | + pprint.pprint(dijkstras) |
| 31 | + |
| 32 | + def run_dijkstras(self, graph, source, destination): |
| 33 | + |
| 34 | + # 1.) Create a shortest path tree set, initially empty |
| 35 | + # Optimization - use a parent path dict to retain previous short path |
| 36 | + shortest_path_tree = [] |
| 37 | + previous_vertices = {vertex: None for vertex in graph.vertices} |
| 38 | + |
| 39 | + # 2.) Creates a map of each vertex (variable) to infinity |
| 40 | + # Set the source to 0 |
| 41 | + distances = {vertex: inf for vertex in graph.vertices} |
| 42 | + distances[source] = 0 |
| 43 | + |
| 44 | + # 3.) While we have visited all vertices in set |
| 45 | + # Technique is to create a copy, and remove from set as its consumed |
| 46 | + vertices = graph.vertices.copy() |
| 47 | + while vertices: |
| 48 | + |
| 49 | + # 4.) Select node with the minimum distance value |
| 50 | + # Once we pull from minimum distance, remove from our vertices |
| 51 | + next_node = min(vertices, key=lambda key: distances[key]) |
| 52 | + vertices.remove(next_node) |
| 53 | + |
| 54 | + # 5.) Add the minimum distance node to set |
| 55 | + # ... As long as we're not left with all unreachable vertices |
| 56 | + if distances[next_node] == inf: |
| 57 | + break |
| 58 | + shortest_path_tree.append(next_node) |
| 59 | + distance = distances[next_node] |
| 60 | + |
| 61 | + # 6.) Update the distance values the next node's neighbors |
| 62 | + neighbors = graph.neighbours[next_node] |
| 63 | + for neighbor, cost in neighbors: |
| 64 | + neighbor_distance = distance + cost |
| 65 | + |
| 66 | + # ... either if we've found a shorter distance to neighbor |
| 67 | + # or we haven't been here before |
| 68 | + if neighbor not in distances or \ |
| 69 | + (neighbor in distances |
| 70 | + and neighbor_distance < distances[neighbor]): |
| 71 | + distances[neighbor] = neighbor_distance |
| 72 | + previous_vertices[neighbor] = next_node |
| 73 | + |
| 74 | + # 7.) Work our way backwards from the destination and |
| 75 | + # Find the shortest path by looking up in the previous_vertices map |
| 76 | + shortest_path, current_vertex = deque(), destination |
| 77 | + while previous_vertices[current_vertex] is not None: |
| 78 | + shortest_path.appendleft(current_vertex) |
| 79 | + current_vertex = previous_vertices[current_vertex] |
| 80 | + |
| 81 | + # When we reach the source, append to the path |
| 82 | + if shortest_path: |
| 83 | + shortest_path.appendleft(current_vertex) |
| 84 | + return shortest_path, distances |
| 85 | + |
0 commit comments