From 3d73700a62c6d8405b20c56b814714d4e387afaa Mon Sep 17 00:00:00 2001 From: Hamid Gasmi Date: Thu, 30 Jun 2022 07:48:50 -0700 Subject: [PATCH 01/10] #297: implement dijkstra algorithm --- .../graph_fastest_path_dijkstra.py | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 10-algo-ds-implementations/graph_fastest_path_dijkstra.py diff --git a/10-algo-ds-implementations/graph_fastest_path_dijkstra.py b/10-algo-ds-implementations/graph_fastest_path_dijkstra.py new file mode 100644 index 0000000..fbd2bc2 --- /dev/null +++ b/10-algo-ds-implementations/graph_fastest_path_dijkstra.py @@ -0,0 +1,75 @@ +import heapq +from typing import List +from .graph import Positively_Weighted_Edge + +''' + Dijkstra's Algorithm + Input: Weighted Graph: G(V, E, W), s ∈ V + - Undirected or Directed + - w >= 0 for all w in W + + Output: + - fastest path from s to any v in V + - fastest path distances from a to any v in V + + Assumptions: + - Directed graph + - Edges number starts from 0 (zero-based numbering) + - Edges list is valid + - 0 <= edge.source, edge.sink < vertices_count for any edge in edges list + - It does not have duplicates + + Time and Space Complexity: + - Time Complexity: + - Space Complexity: +''' +class Dijkstra: + def __init__(self, vertices_count: int, edges: List[Positively_Weighted_Edge], s: int): + self.__max_distance = 10**7 + + self.__adjacency_list = self.__build_adjacency_list(vertices_count, edges) + + self.__path_source_node = s + self.__parents = [] + self.__distances = [] + + def fastest_distance_to(self, v: int) -> int: + return self.__distances[v] + + def fastest_path_to(self, v: int) -> List[int]: + reversed_path = [] + while v != -1: + reversed_path.append(v) + v = self.__parents[v] + + return reversed_path[::-1] + + def __compute_fastest_path (self, v: int) -> List[int]: + vertices_count = len(self.__adjacency_list) + self.__parents = [ -1 for _ in range(vertices_count) ] + self.__distances = [ self.__max_distance for _ in range(vertices_count) ] + + self.__distances[self.__path_source_node] = 0 + closest_nodes_queue = [ (edge.weight, edge.sink) for edge in self.__adjacency_list[self.__path_source_node] ] + closest_nodes_queue.append((0, self.__path_source_node)) + heapq.heapify(closest_nodes_queue) + + while closest_nodes_queue: + (distance, node) = heapq.heappop(closest_nodes_queue) + + for edge in self.__adjacency_list[node]: + adjacent = edge.sink + candidate_distance = distance + edge.weight + if candidate_distance >= self.__distances[ adjacent ]: + continue + + self.__distances[ adjacent ] = candidate_distance + self.__parents[ adjacent ] = node + heapq.heappush(closest_nodes_queue, (candidate_distance, adjacent)) + + def __build_adjacency_list(self, vertices_count: int, edges: List[Positively_Weighted_Edge]) -> List[List[Positively_Weighted_Edge]]: + adjacency_list = [ [] for _ in range(vertices_count) ] + for edge in edges: + adjacency_list[edge.source] = edge + + return adjacency_list \ No newline at end of file From 19b095bf75e6cda6b270e02a0af7ec28afcf6d10 Mon Sep 17 00:00:00 2001 From: Hamid Gasmi Date: Fri, 1 Jul 2022 11:09:27 -0700 Subject: [PATCH 02/10] #297: init heap q with start node only --- .../graph_fastest_path_dijkstra.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/10-algo-ds-implementations/graph_fastest_path_dijkstra.py b/10-algo-ds-implementations/graph_fastest_path_dijkstra.py index fbd2bc2..b03fa90 100644 --- a/10-algo-ds-implementations/graph_fastest_path_dijkstra.py +++ b/10-algo-ds-implementations/graph_fastest_path_dijkstra.py @@ -50,9 +50,7 @@ def __compute_fastest_path (self, v: int) -> List[int]: self.__distances = [ self.__max_distance for _ in range(vertices_count) ] self.__distances[self.__path_source_node] = 0 - closest_nodes_queue = [ (edge.weight, edge.sink) for edge in self.__adjacency_list[self.__path_source_node] ] - closest_nodes_queue.append((0, self.__path_source_node)) - heapq.heapify(closest_nodes_queue) + closest_nodes_queue = [ (0, self.__path_source_node) ] while closest_nodes_queue: (distance, node) = heapq.heappop(closest_nodes_queue) @@ -60,12 +58,10 @@ def __compute_fastest_path (self, v: int) -> List[int]: for edge in self.__adjacency_list[node]: adjacent = edge.sink candidate_distance = distance + edge.weight - if candidate_distance >= self.__distances[ adjacent ]: - continue - - self.__distances[ adjacent ] = candidate_distance - self.__parents[ adjacent ] = node - heapq.heappush(closest_nodes_queue, (candidate_distance, adjacent)) + if candidate_distance < self.__distances[ adjacent ]: + self.__parents[ adjacent ] = node + self.__distances[ adjacent ] = candidate_distance + heapq.heappush(closest_nodes_queue, (candidate_distance, adjacent)) def __build_adjacency_list(self, vertices_count: int, edges: List[Positively_Weighted_Edge]) -> List[List[Positively_Weighted_Edge]]: adjacency_list = [ [] for _ in range(vertices_count) ] From 41ac93681849188e81be51f1f19959db43064f5c Mon Sep 17 00:00:00 2001 From: Hamid Gasmi Date: Sat, 2 Jul 2022 10:59:53 -0700 Subject: [PATCH 03/10] #297: add validations --- .../graph_fastest_path_dijkstra.py | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/10-algo-ds-implementations/graph_fastest_path_dijkstra.py b/10-algo-ds-implementations/graph_fastest_path_dijkstra.py index b03fa90..67e1c0e 100644 --- a/10-algo-ds-implementations/graph_fastest_path_dijkstra.py +++ b/10-algo-ds-implementations/graph_fastest_path_dijkstra.py @@ -6,50 +6,58 @@ Dijkstra's Algorithm Input: Weighted Graph: G(V, E, W), s ∈ V - Undirected or Directed - - w >= 0 for all w in W + - We >= 0 for all e in E Output: - fastest path from s to any v in V - - fastest path distances from a to any v in V - - Assumptions: + - fastest path distances from s to any v in V + + Time and Space Complexity: + - Time Complexity: + - Space Complexity: + + Implementation Assumptions: - Directed graph - Edges number starts from 0 (zero-based numbering) - Edges list is valid - 0 <= edge.source, edge.sink < vertices_count for any edge in edges list - It does not have duplicates - Time and Space Complexity: - - Time Complexity: - - Space Complexity: ''' class Dijkstra: def __init__(self, vertices_count: int, edges: List[Positively_Weighted_Edge], s: int): self.__max_distance = 10**7 - self.__adjacency_list = self.__build_adjacency_list(vertices_count, edges) + self.__adjacency_list = self.__build_adjacency_list(vertices_count, edges) # O(|E|) + self.__nodes_count = len(self.__adjacency_list) self.__path_source_node = s - self.__parents = [] - self.__distances = [] + self.__parent = [] + self.__distance = [] - def fastest_distance_to(self, v: int) -> int: - return self.__distances[v] + def fastest_distance_to(self, dest: int) -> int: + if len(self.__distance) == 0: + self.__compute_fastest_path() + + return self.__distance[dest] - def fastest_path_to(self, v: int) -> List[int]: + def fastest_path_to(self, dest: int) -> List[int]: + if len(self.__distance) == 0: + self.__compute_fastest_path() + + v = dest reversed_path = [] while v != -1: reversed_path.append(v) - v = self.__parents[v] + v = self.__parent[v] return reversed_path[::-1] - def __compute_fastest_path (self, v: int) -> List[int]: - vertices_count = len(self.__adjacency_list) - self.__parents = [ -1 for _ in range(vertices_count) ] - self.__distances = [ self.__max_distance for _ in range(vertices_count) ] + def __compute_fastest_path (self) -> List[int]: + self.__parent = [ -1 for _ in range(self.__nodes_count) ] # O(|V|) + self.__distance = [ self.__max_distance for _ in range(self.__nodes_count) ] # O(|V|) - self.__distances[self.__path_source_node] = 0 + self.__distance[self.__path_source_node] = 0 closest_nodes_queue = [ (0, self.__path_source_node) ] while closest_nodes_queue: @@ -58,9 +66,9 @@ def __compute_fastest_path (self, v: int) -> List[int]: for edge in self.__adjacency_list[node]: adjacent = edge.sink candidate_distance = distance + edge.weight - if candidate_distance < self.__distances[ adjacent ]: - self.__parents[ adjacent ] = node - self.__distances[ adjacent ] = candidate_distance + if candidate_distance < self.__distance[ adjacent ]: + self.__parent[ adjacent ] = node + self.__distance[ adjacent ] = candidate_distance heapq.heappush(closest_nodes_queue, (candidate_distance, adjacent)) def __build_adjacency_list(self, vertices_count: int, edges: List[Positively_Weighted_Edge]) -> List[List[Positively_Weighted_Edge]]: From 0ddd6a8db3993209edacd4e6ba3ecb1eacf65a56 Mon Sep 17 00:00:00 2001 From: Hamid Gasmi Date: Sat, 2 Jul 2022 11:13:14 -0700 Subject: [PATCH 04/10] #297: use nametuple to define Edge datastructure --- .../graph_fastest_path_dijkstra.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/10-algo-ds-implementations/graph_fastest_path_dijkstra.py b/10-algo-ds-implementations/graph_fastest_path_dijkstra.py index 67e1c0e..0356d3f 100644 --- a/10-algo-ds-implementations/graph_fastest_path_dijkstra.py +++ b/10-algo-ds-implementations/graph_fastest_path_dijkstra.py @@ -1,6 +1,6 @@ import heapq from typing import List -from .graph import Positively_Weighted_Edge +from collections import namedtuple ''' Dijkstra's Algorithm @@ -24,8 +24,11 @@ - It does not have duplicates ''' + +Edge = namedtuple('Edge', ['source', 'sink', 'weight']) + class Dijkstra: - def __init__(self, vertices_count: int, edges: List[Positively_Weighted_Edge], s: int): + def __init__(self, vertices_count: int, edges: List[Edge], s: int): self.__max_distance = 10**7 self.__adjacency_list = self.__build_adjacency_list(vertices_count, edges) # O(|E|) @@ -71,9 +74,10 @@ def __compute_fastest_path (self) -> List[int]: self.__distance[ adjacent ] = candidate_distance heapq.heappush(closest_nodes_queue, (candidate_distance, adjacent)) - def __build_adjacency_list(self, vertices_count: int, edges: List[Positively_Weighted_Edge]) -> List[List[Positively_Weighted_Edge]]: + def __build_adjacency_list(self, vertices_count: int, edges: List[Edge]) -> List[List[Edge]]: adjacency_list = [ [] for _ in range(vertices_count) ] for edge in edges: + assert(edge.weight >= 0) adjacency_list[edge.source] = edge return adjacency_list \ No newline at end of file From 82180f766330174c8bf0048012b77980b28653df Mon Sep 17 00:00:00 2001 From: Hamid Gasmi Date: Sat, 2 Jul 2022 12:39:18 -0700 Subject: [PATCH 05/10] #297: implemented Dijkstas algo with heap queue --- ...y => graph_fastest_path_dijkstra_heapq.py} | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) rename 10-algo-ds-implementations/{graph_fastest_path_dijkstra.py => graph_fastest_path_dijkstra_heapq.py} (72%) diff --git a/10-algo-ds-implementations/graph_fastest_path_dijkstra.py b/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py similarity index 72% rename from 10-algo-ds-implementations/graph_fastest_path_dijkstra.py rename to 10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py index 0356d3f..35ef377 100644 --- a/10-algo-ds-implementations/graph_fastest_path_dijkstra.py +++ b/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py @@ -13,8 +13,12 @@ - fastest path distances from s to any v in V Time and Space Complexity: - - Time Complexity: - - Space Complexity: + - Time Complexity: O(|V| + |E|log|E|) + - T(Initialization) + T(Compute Min Distance) = O(|V|) + P(|E|*log|E|) + - Dense graph (|E| = O(|V|^2)) --> T = O(|V| + |V|^2 * log|V|^2) = O(|V|^2 * log|V|) + - Sparce graph (|E| ~ |V|) --> T = O(|V| + |V|log|V|) = O(|V|log|V|) + - Space Complexity: O(|V| + |E|) + - S(Parent list) + S(distance list) + S(Heap queue) = O(|V| + |V| + |E|) = O(|V| + |E|) Implementation Assumptions: - Directed graph @@ -27,7 +31,7 @@ Edge = namedtuple('Edge', ['source', 'sink', 'weight']) -class Dijkstra: +class Dijkstra_Heap_Queue: def __init__(self, vertices_count: int, edges: List[Edge], s: int): self.__max_distance = 10**7 @@ -57,22 +61,24 @@ def fastest_path_to(self, dest: int) -> List[int]: return reversed_path[::-1] def __compute_fastest_path (self) -> List[int]: + # Initialization self.__parent = [ -1 for _ in range(self.__nodes_count) ] # O(|V|) self.__distance = [ self.__max_distance for _ in range(self.__nodes_count) ] # O(|V|) self.__distance[self.__path_source_node] = 0 closest_nodes_queue = [ (0, self.__path_source_node) ] - while closest_nodes_queue: - (distance, node) = heapq.heappop(closest_nodes_queue) + # Computing min distance for each v in V + while closest_nodes_queue: # T(Push to hq) + T(Pop from hq) = O(2*|E|*log|E|) = O(|E|*log|E|): + (distance, node) = heapq.heappop(closest_nodes_queue) # we can pop at most |E| edges from the hq - for edge in self.__adjacency_list[node]: + for edge in self.__adjacency_list[node]: adjacent = edge.sink - candidate_distance = distance + edge.weight + candidate_distance = distance + edge.weight if candidate_distance < self.__distance[ adjacent ]: self.__parent[ adjacent ] = node self.__distance[ adjacent ] = candidate_distance - heapq.heappush(closest_nodes_queue, (candidate_distance, adjacent)) + heapq.heappush(closest_nodes_queue, (candidate_distance, adjacent)) # we can push at most |E| edges into the hq def __build_adjacency_list(self, vertices_count: int, edges: List[Edge]) -> List[List[Edge]]: adjacency_list = [ [] for _ in range(vertices_count) ] From 527056aad6e3da4e57aea72d040a82be4721bcc8 Mon Sep 17 00:00:00 2001 From: Hamid Gasmi Date: Sat, 2 Jul 2022 12:57:05 -0700 Subject: [PATCH 06/10] #297: add comments --- .../graph_fastest_path_dijkstra_heapq.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py b/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py index 35ef377..65090aa 100644 --- a/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py +++ b/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py @@ -78,7 +78,12 @@ def __compute_fastest_path (self) -> List[int]: if candidate_distance < self.__distance[ adjacent ]: self.__parent[ adjacent ] = node self.__distance[ adjacent ] = candidate_distance - heapq.heappush(closest_nodes_queue, (candidate_distance, adjacent)) # we can push at most |E| edges into the hq + + # we can push at most |E| edges into the hq + heapq.heappush(closest_nodes_queue, (candidate_distance, adjacent)) + # We could build a custom minheap that can return the position of an element in O(1). + # We can then change its priority in O(log|V|): heapq.changepriority(closest_nodes_queue, adjacent, candidate_distance) + # The heap queue will contain then at most: |V| elements (instead of |E| element as it's implemented here) def __build_adjacency_list(self, vertices_count: int, edges: List[Edge]) -> List[List[Edge]]: adjacency_list = [ [] for _ in range(vertices_count) ] From f4de10291df32287791e4533bd57f20c30972ecc Mon Sep 17 00:00:00 2001 From: Hamid Gasmi Date: Sat, 2 Jul 2022 13:19:30 -0700 Subject: [PATCH 07/10] #297: implement graph with adjacency list in a separate class --- 10-algo-ds-implementations/graph.py | 33 ++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/10-algo-ds-implementations/graph.py b/10-algo-ds-implementations/graph.py index 7d2e27f..3f4b1f0 100644 --- a/10-algo-ds-implementations/graph.py +++ b/10-algo-ds-implementations/graph.py @@ -1,11 +1,28 @@ +from typing import List +from collections import namedtuple +''' + Build Adjacency List + Input: Eges List + Output: + - Graph implemented with an adjacency list + + Time and Space Complexity: + - Time Complexity: O(|E|) + - Space Complexity: O(|V| + |E|) + +''' -class Weighted_Edge: - def __init__(self, source: int, sink: int, weight: int): - self.source = source - self.sink = sink - self.weight = weight +Edge = namedtuple('Edge', ['source', 'sink', 'weight']) -class Positively_Weighted_Edge(Weighted_Edge): - def __init__(self, source: int, sink: int, weight: int): - assert(weight >= 0) \ No newline at end of file +class Graph_Adjacency_List: + def __init__(self, vertices_count: int, edges: List[Edge]): + self.vertices_count = vertices_count + self.adjacency_list = self.__build_adjacency_list(edges) # O(|E|) + + def __build_adjacency_list(self, edges: List[Edge]) -> List[List[Edge]]: + adjacency_list = [ [] for _ in range(self.__vertices_count) ] + for edge in edges: + adjacency_list[edge.source] = edge + + return adjacency_list \ No newline at end of file From cbb8eedfdfe8f7237d926381e93dd64ec957f22e Mon Sep 17 00:00:00 2001 From: Hamid Gasmi Date: Sat, 2 Jul 2022 13:20:42 -0700 Subject: [PATCH 08/10] #297: use graph class --- .../graph_fastest_path_dijkstra_heapq.py | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py b/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py index 65090aa..9388980 100644 --- a/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py +++ b/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py @@ -1,6 +1,6 @@ import heapq from typing import List -from collections import namedtuple +from graph import Graph_Adjacency_List ''' Dijkstra's Algorithm @@ -29,14 +29,11 @@ ''' -Edge = namedtuple('Edge', ['source', 'sink', 'weight']) - class Dijkstra_Heap_Queue: - def __init__(self, vertices_count: int, edges: List[Edge], s: int): + def __init__(self, graph: Graph_Adjacency_List, s: int): self.__max_distance = 10**7 - self.__adjacency_list = self.__build_adjacency_list(vertices_count, edges) # O(|E|) - self.__nodes_count = len(self.__adjacency_list) + self.__graph = graph self.__path_source_node = s self.__parent = [] @@ -62,8 +59,8 @@ def fastest_path_to(self, dest: int) -> List[int]: def __compute_fastest_path (self) -> List[int]: # Initialization - self.__parent = [ -1 for _ in range(self.__nodes_count) ] # O(|V|) - self.__distance = [ self.__max_distance for _ in range(self.__nodes_count) ] # O(|V|) + self.__parent = [ -1 for _ in range(self.__graph.vertices_count) ] # O(|V|) + self.__distance = [ self.__max_distance for _ in range(self.__graph.vertices_count) ] # O(|V|) self.__distance[self.__path_source_node] = 0 closest_nodes_queue = [ (0, self.__path_source_node) ] @@ -72,7 +69,7 @@ def __compute_fastest_path (self) -> List[int]: while closest_nodes_queue: # T(Push to hq) + T(Pop from hq) = O(2*|E|*log|E|) = O(|E|*log|E|): (distance, node) = heapq.heappop(closest_nodes_queue) # we can pop at most |E| edges from the hq - for edge in self.__adjacency_list[node]: + for edge in self.__graph.adjacency_list[node]: adjacent = edge.sink candidate_distance = distance + edge.weight if candidate_distance < self.__distance[ adjacent ]: @@ -84,11 +81,4 @@ def __compute_fastest_path (self) -> List[int]: # We could build a custom minheap that can return the position of an element in O(1). # We can then change its priority in O(log|V|): heapq.changepriority(closest_nodes_queue, adjacent, candidate_distance) # The heap queue will contain then at most: |V| elements (instead of |E| element as it's implemented here) - - def __build_adjacency_list(self, vertices_count: int, edges: List[Edge]) -> List[List[Edge]]: - adjacency_list = [ [] for _ in range(vertices_count) ] - for edge in edges: - assert(edge.weight >= 0) - adjacency_list[edge.source] = edge - - return adjacency_list \ No newline at end of file + \ No newline at end of file From 7ac383ad37d2cbdd46a31e683da2236f7da21406 Mon Sep 17 00:00:00 2001 From: Hamid Gasmi Date: Sat, 2 Jul 2022 13:24:10 -0700 Subject: [PATCH 09/10] #297: update function signature in heapq implementation --- 10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py b/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py index 9388980..f411002 100644 --- a/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py +++ b/10-algo-ds-implementations/graph_fastest_path_dijkstra_heapq.py @@ -57,7 +57,7 @@ def fastest_path_to(self, dest: int) -> List[int]: return reversed_path[::-1] - def __compute_fastest_path (self) -> List[int]: + def __compute_fastest_path (self): # Initialization self.__parent = [ -1 for _ in range(self.__graph.vertices_count) ] # O(|V|) self.__distance = [ self.__max_distance for _ in range(self.__graph.vertices_count) ] # O(|V|) From 071f9a8c513a63cb53178b54d7f1205a6c90ccfa Mon Sep 17 00:00:00 2001 From: Hamid Gasmi Date: Sat, 2 Jul 2022 13:58:28 -0700 Subject: [PATCH 10/10] #297 implement dijkstras algo with array --- .../graph_fastest_path_dijkstra_array.py | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 10-algo-ds-implementations/graph_fastest_path_dijkstra_array.py diff --git a/10-algo-ds-implementations/graph_fastest_path_dijkstra_array.py b/10-algo-ds-implementations/graph_fastest_path_dijkstra_array.py new file mode 100644 index 0000000..5134a70 --- /dev/null +++ b/10-algo-ds-implementations/graph_fastest_path_dijkstra_array.py @@ -0,0 +1,88 @@ +from typing import List +from graph import Graph_Adjacency_List + +''' + Dijkstra's Algorithm + Input: Weighted Graph: G(V, E, W), s ∈ V + - Undirected or Directed + - We >= 0 for all e in E + + Output: + - fastest path from s to any v in V + - fastest path distances from s to any v in V + + Time and Space Complexity: + - Time Complexity: O(|V|^2) + - T(Initialization) + T(Compute Min Distance) = O(|V|) + O(|V|^2 + |E|) + - Dense graph (|E| = O(|V|^2)) --> T = O(|V|^2 + |V|^2) = O(|V|^2) + - Sparce graph (|E| ~ |V|) --> T = O(|V|^2 + |V|) = O(|V|^2) + - Space Complexity: O(|V|) + - S(Parent list) + S(distance list) + S(visited nodes set) = O(|V| + |V| + |V|) = O(|V|) + +''' + +Candidate = namedtuple('Candidate', ['distance', 'node']) + +class Dijkstra_Heap_Queue: + def __init__(self, graph: Graph_Adjacency_List, s: int): + self.__max_distance = 10**7 + + self.__graph = graph + + self.__path_source_node = s + self.__parent = [] + self.__distance = [] + + def fastest_distance_to(self, dest: int) -> int: + if len(self.__distance) == 0: + self.__compute_fastest_path() + + return self.__distance[dest] + + def fastest_path_to(self, dest: int) -> List[int]: + if len(self.__distance) == 0: + self.__compute_fastest_path() + + v = dest + reversed_path = [] + while v != -1: + reversed_path.append(v) + v = self.__parent[v] + + return reversed_path[::-1] + + # O(|V|) + def __get_closest_node(self, visited_nodes: set) -> int: + closest_node = -1 + min_distance = self.__max_distance + for candidate in range(self.__graph.vertices_count): + if candidate not in visited_nodes and self.__distance[candidate] < min_distance: + closest_node = candidate + min_distance = self.__distance[candidate] + + return closest_node + + def __compute_fastest_path (self): + # Initialization + self.__parent = [ -1 for _ in range(self.__graph.vertices_count) ] # O(|V|) + self.__distance = [ self.__max_distance for _ in range(self.__graph.vertices_count) ] # O(|V|) + + self.__distance[self.__path_source_node] = 0 + visted_nodes = set() + + # Computing min distance for each v in V + closest_node = 0 + while closest_node != -1: # |V| * T(self.__get_closest_node) + Sum(indegree(closest_node)) = |V|^2 + |E| + distance = self.__distance[closest_node] + visted_nodes.add(closest_node) + + for edge in self.__graph.adjacency_list[closest_node]: # O(indegree(closest_node)) + adjacent = edge.sink + candidate_distance = distance + edge.weight + if candidate_distance < self.__distance[ adjacent ]: + self.__parent[ adjacent ] = closest_node + self.__distance[ adjacent ] = candidate_distance + + closest_node = self.__get_closest_node(visted_nodes) # O(|V|) + + \ No newline at end of file