2642. Design Graph With Shortest Path Calculator
Problem Description
In this problem, we are tasked with implementing a class, [Graph](/problems/graph_intro)
, that represents a directed weighted graph. A graph is composed of nodes (or vertices) connected by edges, each with an associated cost. The Graph
class is supposed to support the following functionalities:
-
Initialization with a specific number of nodes and a list of edges, where each edge is a triplet
[from, to, edgeCost]
representing an edge from nodefrom
to nodeto
with a costedgeCost
. -
Adding a new edge to the graph which did not previously exist between two nodes.
-
Finding the shortest path between any two nodes, which is the path where the sum of the costs of its constituent edges is the lowest possible. If no such path exists, the method should return
-1
.
The challenge in this problem is efficiently implementing these functionalities, particularly the path-finding operation, which is central to many applications in network analysis, logistics, and more.
Intuition
The intuition behind the solution lies in implementing effective ways to manage and query the graph upon which we can apply a shortest path algorithm. To represent the graph internally, we use a two-dimensional matrix (self.g
). This allows us to quickly access the cost of any edge given a starting and ending node, defaulting to 'infinity' (inf
) where edges do not exist.
For the shortest path algorithm, we can use a variant of Dijkstra's algorithm, which is well-known for finding the shortest paths between nodes in a graph with non-negative edge weights. Here's the high-level approach:
-
Initialize a
dist
array, representing the shortest discovered distances fromnode1
to every other node, with infinite values except for the starting node (node1
), which has a distance of zero. -
Create a
vis
array to keep track of nodes whose shortest distance is finalized and will no longer change. -
Repeat the following steps
n
times (wheren
is the number of nodes in the graph):a. Select the unvisited node with the current smallest known distance (
t
).b. Mark this node as visited.
c. Update the
dist
values of adjacent nodes using the edge costs from the selected node if it results in a shorter path than what is currently known. -
Once completed, the
dist
array contains the shortest known distances fromnode1
to all other nodes. Specifically,dist[node2]
will give us the minimum cost of the path fromnode1
tonode2
. If this value isinf
, it means no path exists, and we return-1
.
With this approach, addEdge
simply updates the edge matrix self.g
with the new edge cost, whereas shortestPath
performs Dijkstra's algorithm as described to return the shortest path cost between two given nodes.
Learn more about Graph, Shortest Path and Heap (Priority Queue) patterns.
Solution Approach
The implementation of the [Graph](/problems/graph_intro)
class consists of several components, each handling a distinct aspect of the graph's functionality. Here's a detailed breakdown:
-
Initialization (
__init__
method):- We initialize a 2D matrix
self.g
with sizen * n
, wheren
is the number of nodes. This matrix will hold the edge costs, initialized to 'infinity' (inf
) to represent no direct edge between nodes. This value is symbolic for the absence of a path and ensures it doesn't interfere with the calculation of minimum costs. - For each edge in the input list
edges
, we populateself.g
with the corresponding edge cost at the position[from_i][to_i]
, wherefrom_i
andto_i
are the starting and ending nodes of the edgei
with costedgeCost_i
.
- We initialize a 2D matrix
-
Adding an Edge (
addEdge
method):- It's a straightforward update of the edge cost matrix
self.g
for the new edge specified by theedge
parameter. - The new edge's starting node, ending node, and cost are extracted from the input
edge
list and used to updateself.g
.
- It's a straightforward update of the edge cost matrix
-
Finding the Shortest Path (
shortestPath
method):- Initialize a list
dist
with values 'infinity' (inf
) for tracking the shortest distance fromnode1
to all other nodes. We setdist[node1]
to0
since the distance fromnode1
to itself is zero. - Create a list
vis
that will track whether we have finalized the shortest distance to a node. Initially, all values are set toFalse
. - We use a for-loop to iterate
n
times, wheren
is the number of nodes:- Within this loop, we select the node with the smallest
dist
value that hasn't been visited yet (t
). This greedy choice leads us to pick the nearest unvisited node. - We mark this node
t
as visited by settingvis[t]
toTrue
. - We then relax the edges by iterating over all nodes (
j
) and checking if the path throught
offers a shorter path. If it does, we updatedist[j]
with the new minimum cost.
- Within this loop, we select the node with the smallest
- Finally, we check if
dist[node2]
is still 'infinity' (inf
) to determine ifnode1
andnode2
are disconnected; if so, we return-1
. Otherwise, we returndist[node2]
, which is the minimum cost of the path found fromnode1
tonode2
.
- Initialize a list
Each step of the shortestPath
method follows the standard Dijkstra's algorithm pattern with a slight modification to adapt it to a dense graph representation (2D matrix). This approach ensures that we can find the shortest path efficiently, even in a graph where the edges are represented with a matrix instead of an adjacency list or other sparse representation.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's use a small example to illustrate the solution approach. Suppose we have a graph with 4 nodes (numbered 0 through 3) and the following directed edges:
- An edge from node
0
to node1
with a cost of3
- An edge from node
1
to node2
with a cost of4
- An edge from node
2
to node3
with a cost of2
- An edge from node
0
to node3
with a cost of10
We want to find the shortest path from node 0
to node 3
.
Step 1: Initialization
We instantiate a Graph
object with 4 nodes and an initial list of edges: [(0, 1, 3), (1, 2, 4), (2, 3, 2), (0, 3, 10)]
.
Step 2: Representation
After initialization, our graph's internal matrix self.g
will look like this, where inf
denotes no direct edge:
0 1 2 3 0 0 3 inf 10 1 inf 0 4 inf 2 inf inf 0 2 3 inf inf inf 0
Step 3: Add a New Edge
Let's say we add a new edge from node 1
to node 3
with a cost of 5
using the addEdge
method:
0 1 2 3 0 0 3 inf 10 1 inf 0 4 5 2 inf inf 0 2 3 inf inf inf 0
Step 4: Finding the Shortest Path
Now, to find the shortest path from node 0
to node 3
, we follow Dijkstra's algorithm:
- Initialize
dist
withinf
and setdist[0]
to0
. - The
vis
array will keep track of visited nodes, starting all asFalse
.
For each iteration:
-
Pick the smallest
dist
value from unvisited nodes. First, it's0
itself, with a distance of0
. -
Visit node
0
, and update distances to its adjacent nodes.dist[1]
becomes3
, anddist[3]
becomes10
. -
Next, pick node
1
since it has the smallestdist
of3
. Visit and update its neighbors. Since1 -> 3
is5
, the total cost0 -> 1 -> 3
would be3 + 5 = 8
, which is less than the existing10
. So,dist[3]
is updated to8
. -
Repeat the process for nodes
2
and3
, but since0 -> 1 -> 3
is already the smallest path to3
, there will be no more updates.
In the end, dist[3]
equals 8
, which is the shortest path cost from 0
to 3
.
This would conclude our example, demonstrating how we would use the provided Graph
class to add edges and find the shortest path between two nodes.
Solution Implementation
1from typing import List
2
3# Define infinity as a variable for ease of use and readability
4inf = float('inf')
5
6class Graph:
7 # Initialize the graph with 'n' nodes and the specified 'edges'.
8 def __init__(self, n: int, edges: List[List[int]]):
9 self.n = n # Number of nodes
10 self.adj_matrix = [[inf] * n for _ in range(n)] # Adjacency matrix to store edge costs
11
12 # Fill the adjacency matrix with the cost of each edge
13 for source, target, cost in edges:
14 self.adj_matrix[source][target] = cost
15
16 # Add an edge to the graph with a source, target, and cost.
17 def add_edge(self, edge: List[int]) -> None:
18 source, target, cost = edge
19 self.adj_matrix[source][target] = cost
20
21 # Compute the shortest path from 'node1' to 'node2' using Dijkstra's algorithm.
22 def shortest_path(self, node1: int, node2: int) -> int:
23 dist = [inf] * self.n # Distances array
24 dist[node1] = 0 # Distance to starting node is always 0
25 visited = [False] * self.n # Visited array to mark nodes
26
27 # Perform Dijkstra's algorithm to find the shortest paths
28 for _ in range(self.n):
29 closest_node = -1
30 for j in range(self.n):
31 if not visited[j] and (closest_node == -1 or dist[closest_node] > dist[j]):
32 closest_node = j
33 visited[closest_node] = True
34
35 # Update distances to adjacent nodes
36 for j in range(self.n):
37 dist[j] = min(dist[j], dist[closest_node] + self.adj_matrix[closest_node][j])
38
39 # Return the shortest path cost to 'node2', or -1 if 'node2' is not reachable
40 return -1 if dist[node2] == inf else dist[node2]
41
42
43# Example of how the Graph class could be utilized
44# obj = Graph(n, edges)
45# obj.add_edge(edge)
46# param_2 = obj.shortest_path(node1, node2)
47
1import java.util.Arrays;
2
3public class Graph {
4 private int numberOfNodes;
5 private int[][] adjacencyMatrix;
6 private static final int INFINITY = 1 << 29; // A representation of infinity (large value)
7
8 // Constructor to initialize the graph with a number of nodes and edges.
9 public Graph(int numberOfNodes, int[][] edges) {
10 this.numberOfNodes = numberOfNodes;
11 adjacencyMatrix = new int[numberOfNodes][numberOfNodes];
12
13 // Initialize all distances in the adjacency matrix to infinity.
14 for (int[] row : adjacencyMatrix) {
15 Arrays.fill(row, INFINITY);
16 }
17
18 // Fill the adjacency matrix with the given edges and their costs.
19 for (int[] edge : edges) {
20 int from = edge[0], to = edge[1], cost = edge[2];
21 adjacencyMatrix[from][to] = cost;
22 }
23 }
24
25 // Method to add an edge with a cost to the graph.
26 public void addEdge(int[] edge) {
27 int from = edge[0], to = edge[1], cost = edge[2];
28 adjacencyMatrix[from][to] = cost;
29 }
30
31 // Method to find the shortest path from one node to another using Dijkstra's algorithm.
32 public int shortestPath(int sourceNode, int targetNode) {
33 int[] distances = new int[numberOfNodes];
34 boolean[] visited = new boolean[numberOfNodes];
35
36 // Initialize all distances to infinity, except the distance to the source node itself.
37 Arrays.fill(distances, INFINITY);
38 distances[sourceNode] = 0;
39
40 // Iterate through all nodes to pick the one with the minimum distance, which is not yet visited.
41 for (int i = 0; i < numberOfNodes; ++i) {
42 int closest = -1;
43 for (int j = 0; j < numberOfNodes; ++j) {
44 if (!visited[j] && (closest == -1 || distances[closest] > distances[j])) {
45 closest = j;
46 }
47 }
48 visited[closest] = true;
49
50 // Update the distance for each node adjacent to the currently selected node.
51 for (int j = 0; j < numberOfNodes; ++j) {
52 distances[j] = Math.min(distances[j], distances[closest] + adjacencyMatrix[closest][j]);
53 }
54 }
55
56 // Return the shortest distance to the target node, or -1 if not reachable.
57 return distances[targetNode] >= INFINITY ? -1 : distances[targetNode];
58 }
59
60 //...
61
62 // Note: If the Graph class is part of a larger file or project, keep the ellipsis and add additional methods here.
63}
64
65/**
66 * Usage example (outside the Graph class):
67 * Graph obj = new Graph(n, edges);
68 * obj.addEdge(edge);
69 * int shortestDistance = obj.shortestPath(node1, node2);
70 */
71
1#include <vector>
2#include <algorithm>
3#include <climits> // for INT_MAX
4
5using namespace std;
6
7class Graph {
8public:
9 // Construct a graph with n nodes and initialized edges
10 Graph(int n, const vector<vector<int>>& edges) {
11 this->numNodes = n;
12 // Initialize graph with 'numNodes' and set distances to infinity (INT_MAX represents infinity)
13 adjMatrix = vector<vector<int>>(numNodes, vector<int>(numNodes, INT_MAX));
14 // Set edge distances based on the input edges
15 for (const auto& edge : edges) {
16 int from = edge[0], to = edge[1], cost = edge[2];
17 adjMatrix[from][to] = cost;
18 }
19 }
20
21 // Adds a new edge to the graph
22 void addEdge(const vector<int>& edge) {
23 int from = edge[0], to = edge[1], cost = edge[2];
24 adjMatrix[from][to] = cost;
25 }
26
27 // Computes the shortest path between node1 and node2 using Dijkstra's algorithm
28 int shortestPath(int startNode, int endNode) {
29 vector<bool> visited(numNodes, false);
30 vector<int> distances(numNodes, INT_MAX);
31 distances[startNode] = 0;
32
33 // Iterate 'numNodes' times to find the shortest paths
34 for (int i = 0; i < numNodes; ++i) {
35 int closest = -1;
36 // Find the nearest unvisited node
37 for (int j = 0; j < numNodes; ++j) {
38 if (!visited[j] && (closest == -1 || distances[closest] > distances[j])) {
39 closest = j;
40 }
41 }
42 visited[closest] = true; // Mark the node as visited
43
44 // Update the distances of the adjacent nodes
45 for (int j = 0; j < numNodes; ++j) {
46 // Safe addition to avoid integer overflow
47 if(distances[closest] != INT_MAX && adjMatrix[closest][j] != INT_MAX) {
48 distances[j] = min(distances[j], distances[closest] + adjMatrix[closest][j]);
49 }
50 }
51 }
52 // Return the shortest path to 'endNode', or -1 if it's not reachable
53 return distances[endNode] == INT_MAX ? -1 : distances[endNode];
54 }
55
56private:
57 vector<vector<int>> adjMatrix; // Adjacency matrix to represent the graph
58 int numNodes; // Number of nodes in the graph
59};
60
61/**
62 * Your Graph object will be instantiated and called as such:
63 * Graph* obj = new Graph(numNodes, edges);
64 * obj->addEdge(edge);
65 * int param_2 = obj->shortestPath(startNode, endNode);
66 */
67
1// Define the INF constant to represent an unreachable state in the graph.
2const INF: number = 1 << 29;
3
4// Declare the 'graph' variable to store the adjacency matrix of the graph.
5let graph: number[][] = [];
6
7/**
8 * Initializes the graph with a given number of nodes and an array of edges.
9 *
10 * @param n - The number of nodes in the graph.
11 * @param edges - An array of edges where each edge is represented as [from, to, cost].
12 */
13function initializeGraph(n: number, edges: number[][]): void {
14 graph = Array.from({ length: n }, () => Array(n).fill(INF));
15 // Add each edge to the graph.
16 for (const [from, to, cost] of edges) {
17 graph[from][to] = cost;
18 }
19}
20
21/**
22 * Adds a new edge to the graph.
23 *
24 * @param edge - An array representing the new edge as [from, to, cost].
25 */
26function addEdge(edge: number[]): void {
27 const [from, to, cost] = edge;
28 graph[from][to] = cost;
29}
30
31/**
32 * Finds the shortest path between two nodes using Dijkstra's algorithm.
33 *
34 * @param node1 - The starting node.
35 * @param node2 - The destination node.
36 * @returns The shortest path cost or -1 if no path exists.
37 */
38function shortestPath(node1: number, node2: number): number {
39 const n = graph.length;
40 const dist: number[] = new Array(n).fill(INF);
41 dist[node1] = 0;
42 const visited: boolean[] = new Array(n).fill(false);
43
44 // Find the shortest paths to all nodes.
45 for (let i = 0; i < n; ++i) {
46 let nearest = -1;
47 // Select the unvisited node with the smallest distance.
48 for (let j = 0; j < n; ++j) {
49 if (!visited[j] && (nearest === -1 || dist[j] < dist[nearest])) {
50 nearest = j;
51 }
52 }
53 // Mark as visited.
54 visited[nearest] = true;
55 // Update distances to other nodes.
56 for (let j = 0; j < n; ++j) {
57 dist[j] = Math.min(dist[j], dist[nearest] + graph[nearest][j]);
58 }
59 }
60
61 // Return the distance to the destination node, or -1 if it's not reachable.
62 return dist[node2] >= INF ? -1 : dist[node2];
63}
64
65// Example usage:
66// initializeGraph(n, edges);
67// addEdge(edge);
68// const pathCost = shortestPath(node1, node2);
69
Time and Space Complexity
Time Complexity
-
The constructor
__init__
has a time complexity ofO(n^2)
because it initializes ann x n
matrix, where each initialization operation has a constant time complexity, and we performn * n
such operations. Additionally, it iterates through each edge provided to fill in the graph, which adds anO(E)
time complexity whereE
is the number of edges, leading to a total constructor complexity ofO(n^2 + E)
. -
The
addEdge
function has a time complexity ofO(1)
as it performs a constant number of operations regardless of the graph size. -
For the
shortestPath
method, there is a nested loop structure. The outer loop runsn
times, and for each iteration, the inner loops iterate up ton
times to find the unvisited node with the smallest distance and to update the distances. This leads to an overall time complexity ofO(n^2)
for the shortest path calculation using Dijkstra's algorithm without a priority queue.
Space Complexity
-
The space complexity of the class is
O(n^2)
, mainly due to the adjacency matrixself.g
which stores the weights of the edges between nodes. -
The
shortestPath
method uses an additional arraydist
of sizen
and a boolean arrayvis
also of sizen
, which account for a space complexity ofO(n)
. Since this does not dominate theO(n^2)
space complexity of the class, the overall space complexity remainsO(n^2)
.
Learn more about how to find time and space complexity quickly using problem constraints.
What data structure does Breadth-first search typically uses to store intermediate states?
Recommended Readings
https algomonster s3 us east 2 amazonaws com cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could
Shortest Path Between A and B Prereq BFS on Graph problems graph_bfs Given an unweighted connected graph return the length of the shortest path between two nodes A and B in terms of the number of edges Assume there always exists a path between nodes A and B Input graph
https algomonster s3 us east 2 amazonaws com cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue
Want a Structured Path to Master System Design Too? Don’t Miss This!