1
+ import heapq
2
+ from typing import List
3
+ from graph import Graph_Adjacency_List
4
+
5
+ '''
6
+ Dijkstra's Algorithm
7
+ Input: Weighted Graph: G(V, E, W), s ∈ V
8
+ - Undirected or Directed
9
+ - We >= 0 for all e in E
10
+
11
+ Output:
12
+ - fastest path from s to any v in V
13
+ - fastest path distances from s to any v in V
14
+
15
+ Time and Space Complexity:
16
+ - Time Complexity: O(|V| + |E|log|E|)
17
+ - T(Initialization) + T(Compute Min Distance) = O(|V|) + P(|E|*log|E|)
18
+ - Dense graph (|E| = O(|V|^2)) --> T = O(|V| + |V|^2 * log|V|^2) = O(|V|^2 * log|V|)
19
+ - Sparce graph (|E| ~ |V|) --> T = O(|V| + |V|log|V|) = O(|V|log|V|)
20
+ - Space Complexity: O(|V| + |E|)
21
+ - S(Parent list) + S(distance list) + S(Heap queue) = O(|V| + |V| + |E|) = O(|V| + |E|)
22
+
23
+ Implementation Assumptions:
24
+ - Directed graph
25
+ - Edges number starts from 0 (zero-based numbering)
26
+ - Edges list is valid
27
+ - 0 <= edge.source, edge.sink < vertices_count for any edge in edges list
28
+ - It does not have duplicates
29
+
30
+ '''
31
+
32
+ class Dijkstra_Heap_Queue :
33
+ def __init__ (self , graph : Graph_Adjacency_List , s : int ):
34
+ self .__max_distance = 10 ** 7
35
+
36
+ self .__graph = graph
37
+
38
+ self .__path_source_node = s
39
+ self .__parent = []
40
+ self .__distance = []
41
+
42
+ def fastest_distance_to (self , dest : int ) -> int :
43
+ if len (self .__distance ) == 0 :
44
+ self .__compute_fastest_path ()
45
+
46
+ return self .__distance [dest ]
47
+
48
+ def fastest_path_to (self , dest : int ) -> List [int ]:
49
+ if len (self .__distance ) == 0 :
50
+ self .__compute_fastest_path ()
51
+
52
+ v = dest
53
+ reversed_path = []
54
+ while v != - 1 :
55
+ reversed_path .append (v )
56
+ v = self .__parent [v ]
57
+
58
+ return reversed_path [::- 1 ]
59
+
60
+ def __compute_fastest_path (self ):
61
+ # Initialization
62
+ self .__parent = [ - 1 for _ in range (self .__graph .vertices_count ) ] # O(|V|)
63
+ self .__distance = [ self .__max_distance for _ in range (self .__graph .vertices_count ) ] # O(|V|)
64
+
65
+ self .__distance [self .__path_source_node ] = 0
66
+ closest_nodes_queue = [ (0 , self .__path_source_node ) ]
67
+
68
+ # Computing min distance for each v in V
69
+ while closest_nodes_queue : # T(Push to hq) + T(Pop from hq) = O(2*|E|*log|E|) = O(|E|*log|E|):
70
+ (distance , node ) = heapq .heappop (closest_nodes_queue ) # we can pop at most |E| edges from the hq
71
+
72
+ for edge in self .__graph .adjacency_list [node ]:
73
+ adjacent = edge .sink
74
+ candidate_distance = distance + edge .weight
75
+ if candidate_distance < self .__distance [ adjacent ]:
76
+ self .__parent [ adjacent ] = node
77
+ self .__distance [ adjacent ] = candidate_distance
78
+
79
+ # we can push at most |E| edges into the hq
80
+ heapq .heappush (closest_nodes_queue , (candidate_distance , adjacent ))
81
+ # We could build a custom minheap that can return the position of an element in O(1).
82
+ # We can then change its priority in O(log|V|): heapq.changepriority(closest_nodes_queue, adjacent, candidate_distance)
83
+ # The heap queue will contain then at most: |V| elements (instead of |E| element as it's implemented here)
84
+
0 commit comments