Design Graph With Shortest Path Calculator

Updated on 22 May, 2025
Design Graph With Shortest Path Calculator header image

Problem Statement

In this problem, we are working with a directed weighted graph characterized by a set of nodes and directed edges with associated costs. The nodes in the graph are labeled sequentially from 0 to n - 1. The graph's connections are defined by an array edges, where each element edges[i] = [fromi, toi, edgeCosti] represents a directed edge starting at node fromi and ending at node toi with a travel cost of edgeCosti.

We are required to implement a Graph class with specific functionalities:

  • Initialization (Graph(int n, int[][] edges)): Constructs the graph using n nodes and the edges defined by the edges array.
  • Adding an Edge (addEdge(int[] edge)): Incorporates a new directed edge into the graph. Here, edge is represented as [from, to, edgeCost], and it's guaranteed that such an edge did not previously exist between the specified nodes.
  • Finding the Shortest Path (int shortestPath(int node1, int node2)): Determines the minimum cost path from node1 to node2. If such a path does not exist, the method returns -1.

The challenge involves accurately managing and querying the graph structure, especially under changing conditions as new edges are added.

Examples

Example 1

Input:

["Graph", "shortestPath", "shortestPath", "addEdge", "shortestPath"]
[[4, [[0, 2, 5], [0, 1, 2], [1, 2, 1], [3, 0, 3]]], [3, 2], [0, 3], [[1, 3, 4]], [0, 3]]

Output:

[null, 6, -1, null, 6]

Explanation:

Graph g = new Graph(4, [[0, 2, 5], [0, 1, 2], [1, 2, 1], [3, 0, 3]]);
g.shortestPath(3, 2); // return 6. The shortest path from 3 to 2 in the first diagram above is 3 -> 0 -> 1 -> 2 with a total cost of 3 + 2 + 1 = 6.
g.shortestPath(0, 3); // return -1. There is no path from 0 to 3.
g.addEdge([1, 3, 4]); // We add an edge from node 1 to node 3, and we get the second diagram above.
g.shortestPath(0, 3); // return 6. The shortest path from 0 to 3 now is 0 -> 1 -> 3 with a total cost of 2 + 4 = 6.

Constraints

  • 1 <= n <= 100
  • 0 <= edges.length <= n * (n - 1)
  • edges[i].length == edge.length == 3
  • 0 <= fromi, toi, from, to, node1, node2 <= n - 1
  • 1 <= edgeCosti, edgeCost <= 106
  • There are no repeated edges and no self-loops in the graph at any point.
  • At most 100 calls will be made for addEdge.
  • At most 100 calls will be made for shortestPath.

Approach and Intuition

From the example provided, let's break down the approach and methodology to address the tasks defined by the Graph class:

  • Initialization:

    • The graph initializes with a specific number of nodes and all edges listed. This might involve setting up a data structure, typically an adjacency list or matrix, to efficiently store and retrieve the connections and their costs.
  • Adding an Edge:

    • When a new edge is added, it must update the existing structure without violating the graph's properties (no self-loops and no repeated edges). This addition will affect future operations, particularly the path-finding queries.
  • Finding the Shortest Path:

    • The key here is implementing an effective algorithm to find the shortest path. Considering the constraints (e.g., number of nodes up to 100, which is moderately small), algorithms like Dijkstra's, Bellman-Ford, or even Floyd-Warshall can be utilized for path finding in this context. Dijkstra’s algorithm is generally efficient for graphs with non-negative weights, which fits as all edgeCosts are positive.
    • If the method returns -1, it indicates no viable path exists from the start to the target node under current graph conditions.

Taking the example through these points:

  1. Initialization: The graph is set up with the given edges which define direct paths between various nodes and their associated costs.
  2. Query for Shortest Path: Before and after adding new edges, the shortest path costs are assessed.
  3. Modification: Adding a new edge potentially creates new paths or reduces the cost of existing paths, further demonstrating the dynamic nature of path queries with graph modification.

This approach underlies the dynamics of mutable graph structures and emphasizes efficient graph manipulation and query techniques.

Solutions

  • C++
  • Java
  • Python
cpp
class Network {
public:
    vector<vector<int>> distanceMatrix;
    Network(int size, vector<vector<int>>& connections) {
        distanceMatrix = vector<vector<int>>(size, vector<int>(size, 1e9));
        for (auto &connection : connections)
            distanceMatrix[connection[0]][connection[1]] = connection[2];
        for (int i = 0; i < size; ++i)
            distanceMatrix[i][i] = 0;
        for (int i = 0; i < size; i++)
            for (int j = 0; j < size; j++)
                for (int k = 0; k < size; k++)
                    distanceMatrix[j][k] = min(distanceMatrix[j][k],
                                               distanceMatrix[j][i] + distanceMatrix[i][k]);
    }
    
    void connectEdges(vector<int> edgeInfo) {
        int size = distanceMatrix.size();
        for (int i = 0; i < size; i++)
            for (int j = 0; j < size; j++)
                distanceMatrix[i][j] = min(distanceMatrix[i][j], 
                                           distanceMatrix[i][edgeInfo[0]] +
                                           distanceMatrix[edgeInfo[1]][j] +
                                           edgeInfo[2]);
    }
    
    int calculateShortestPath(int startPoint, int endPoint) {
        if (distanceMatrix[startPoint][endPoint] == 1e9) 
            return -1;
        return distanceMatrix[startPoint][endPoint];
    }
};

This solution outlines the implementation of a graph network in C++ capable of calculating the shortest path between any two nodes. Here's an overview of how the implementation works:

  • Initialization of Distance Matrix:

    • Initializes a 2D vector distanceMatrix of size size x size with default values set to a large number (1e9) to represent a large initial distance between nodes, except for distances from a node to itself which is set to 0.
  • Setting Direct Connections:

    • Iterates over the input list of connections. Each connection is a vector where the first two values represent the nodes and the third value represents the distance between these nodes. Updates distanceMatrix accordingly for direct connections.
  • Floyd-Warshall Algorithm:

    • Executes the Floyd-Warshall algorithm to find the shortest path between all pairs of nodes. The algorithm iteratively updates the distanceMatrix to ensure that it holds the shortest possible distances, considering intermediary nodes one by one.
  • Dynamic Addition of Edges:

    • Provides a method connectEdges to add new edges after the initial setup and recalculates the shortest paths by considering the new edge as an intermediary node.
  • Shortest Path Calculation:

    • Offers a method calculateShortestPath that takes a start point and an end point to return the shortest distance between these two nodes. It returns -1 if no path exists.

Review this implementation to integrate graph-based functionality into applications needing efficient path computation between nodes, such as in networks or map-based services. Make use of the provided methods to extend or modify the connectivity graph dynamically and retrieve shortest path distances efficiently.

java
class Network {
    private int[][] distanceMatrix;

    public Network(int size, int[][] links) {
        distanceMatrix = new int[size][size];
        Arrays.stream(distanceMatrix).forEach(r -> Arrays.fill(r, (int)1e9));
        for (int[] link : links) {
            distanceMatrix[link[0]][link[1]] = link[2];
        }
        for (int i = 0; i < size; i++) {
            distanceMatrix[i][i] = 0;
        }
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                for (int k = 0; k < size; k++) {
                    distanceMatrix[j][k] = Math.min(distanceMatrix[j][k], 
                                                    distanceMatrix[j][i] +
                                                    distanceMatrix[i][k]);
                }
            }
        }
    }

    public void insertLink(int[] link) {
        int size = distanceMatrix.length;
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                distanceMatrix[i][j] = Math.min(distanceMatrix[i][j],
                                                distanceMatrix[i][link[0]] +
                                                distanceMatrix[link[1]][j] +
                                                link[2]);
            }
        }
    }

    public int calculateShortestPath(int startPoint, int endPoint) {
        if (distanceMatrix[startPoint][endPoint] == (int)1e9)
            return -1;
        return distanceMatrix[startPoint][endPoint];
    }
}

The Java class described, Network, implements a graph structure which is capable of calculating the shortest paths between nodes using Floyd-Warshall algorithm. This solution is structured as follows:

  • Initialization: In the constructor, the class initializes a two-dimensional array distanceMatrix which represents the distances between each pair of nodes. Initially, all distances are set to a high value (represented by 1e9) indicating that they are practically infinite, except for the diagonal (distance from a node to itself), which is set to zero.

  • Building the Graph: The constructor also accepts an array of links to construct the initial graph. Each link is a three-element array where the first two elements represent the nodes and the third element represents the distance. These input distances overwrite the default values in the distanceMatrix to establish direct connections between nodes.

  • Floyd-Warshall Algorithm Implementation: After setting the initial distances, the constructor uses Floyd-Warshall algorithm. This involves three nested loops iterating over all possible pairs of vertices and attempting to find a shorter path through a third vertex than any previously recorded paths. This updates the distanceMatrix accordingly to reflect the shortest possible distances between all pairs of nodes.

  • Adding New Links Dynamically: The insertLink method allows for dynamic insertion of new links into the network. Whenever a new link is added, the distance matrix is updated not only for the direct connection but also considering potential indirect paths that might become shorter due to the introduction of the new link.

  • Shortest Path Calculation: The calculateShortestPath method returns the shortest distance from a specified start point to an end point using the precomputed distanceMatrix. If no path exists (distance remains set at 1e9), this method returns -1 to indicate such.

This Java implementation effectively sets up a graph structure capable of dynamic updates and efficient shortest path calculations, suitable for applications requiring regular modifications and queries about node connectivity.

python
class Network:

    def __init__(self, vertices, links):
        self.distance_matrix = [[float('inf')] * vertices for _ in range(vertices)]
        for src, dest, weight in links:
            self.distance_matrix[src][dest] = weight
        for v in range(vertices):
            self.distance_matrix[v][v] = 0
        for v in range(vertices):
            for u in range(vertices):
                for w in range(vertices):
                    self.distance_matrix[u][w] = min(self.distance_matrix[u][w],
                                                     self.distance_matrix[u][v] +
                                                     self.distance_matrix[v][w])

    def addLink(self, link: List[int]) -> None:
        start, end, weight = link
        size = len(self.distance_matrix)
        for i in range(size):
            for j in range(size):
                self.distance_matrix[i][j] = min(self.distance_matrix[i][j],
                                                 self.distance_matrix[i][start] + 
                                                 self.distance_matrix[end][j] +
                                                 weight)

    def findShortestPath(self, node1, node2):
        if self.distance_matrix[node1][node2] == float('inf'): return -1
        return self.distance_matrix[node1][node2]

The provided code demonstrates how to design and implement a graph network that allows calculation of the shortest path between any two vertices using the Floyd-Warshall algorithm. This code is implemented in Python and encompasses a class named Network.

  • The __init__ method initializes the graph with a given number of vertices and directed edges with weights. It builds a distance matrix where the entry distance_matrix[i][j] is the shortest distance from vertex i to vertex j.

  • After initial setup populates direct distances and sets zero distance for self-loops, the Floyd-Warshall algorithm is employed, iterating through all vertices and updating the shortest paths.

  • addLink method allows for adding an additional weighted link between two existing nodes in the graph. This dynamically updates the shortest paths in the graph considering this new link by recalculating distances that might be affected.

  • findShortestPath method retrieves the shortest path from one node to another. If no path exists, it returns -1.

This code enables the dynamic updating and querying of shortest paths within a network efficiently, which is especially useful in scenarios like traffic management, routing protocols in networks, or anywhere where dynamic graph properties are crucial.

Comments

No comments yet.