Dijkstra's Shortest Path Algorithm Implementation

YASH SAWARKAR - Aug 18 - - Dev Community

Dijkstra's algorithm is used to find the shortest paths from a source node to all other nodes in a directed or undirected weighted graph. Dijkstra's algorithm is a well-known algorithm for finding the shortest paths between nodes in a graph, particularly useful when dealing with graphs without negative edge weights.

Key Concepts

Dijkstra's Algorithm

  • Purpose: To find the shortest path from a single source node to all other nodes in a graph.
  • Limitations:
    • Not applicable to graphs with negative edge weights.
    • Cannot handle graphs containing negative cycles.
    • Does not provide information about unreachable nodes.

Graph Representation

  • The graph is represented using an adjacency list, where each node is mapped to a list of its neighbors and the respective edge weights.

Code Breakdown

1. Class Definition: DirectedWeightedGraph

  • Adjacency List: The graph is stored as an unordered_map where each node has a list of its neighboring nodes and the weights of the edges connecting them.
  • Functions:
    • insertEdge: Adds an edge to the graph. It supports both directed and undirected graphs.
    • printAdjacencyList: Prints the adjacency list of the graph for debugging purposes.
    • dijkstrasShortestPath: Implements Dijkstra's algorithm to find the shortest path from a source node to all other nodes.

2. Function: insertEdge

  • Parameters:
    • source: The starting node of the edge.
    • destination: The ending node of the edge.
    • weight: The weight of the edge.
    • isDirected: Boolean indicating whether the graph is directed or undirected.
  • Functionality:
    • If the graph is directed, an edge is added from the source to the destination.
    • If the graph is undirected, edges are added in both directions.

3. Function: dijkstrasShortestPath

  • Parameters:
    • source: The source node from which shortest paths are calculated.
    • numberOfNodes: Total number of nodes in the graph.
  • Data Structures:
    • shortestDistances: A vector storing the shortest distance from the source to each node, initialized to INT_MAX to signify infinity.
    • nodesSet: A set that acts as a priority queue, storing pairs of node distances and node values, sorted by distance.
  • Algorithm:
    • The source node is inserted into the set with a distance of 0.
    • The algorithm iterates over the set, picking the node with the smallest distance (similar to BFS).
    • For each node, it checks its neighbors and updates their shortest distances if a shorter path is found through the current node.
    • The updated neighbors are reinserted into the set for further processing.

4. Main Function

  • Graph Construction: The graph is built by inserting edges with specified weights.
  • Shortest Path Calculation: Dijkstra's algorithm is run from a specified source node.
  • Output: The shortest distances from the source node to all other nodes are printed.

Example


#include <climits>
#include <iostream>
#include <list>
#include <ostream>
#include <set>
#include <unordered_map>
#include <utility>
#include <vector>

using namespace std;

// NOTE:
// -> this approach can be used on both directed and undirected graphs
// Limitations of Dijkstra's Algorithm:
//  - Inapplicable to graphs with negative edge weights.
//  - Unable to handle graphs containing negative cycles.
//  - Does not provide information about unreachable nodes.

// Class representing a directed weighted graph
class DirectedWeightedGraph {
public:
  // Adjacency List representation: <vertex value, <neighbor, weight>>
  unordered_map<int, list<pair<int, int>>> adjacencyList;

  // Function to insert an edge into the graph
  void insertEdge(int source, int destination, int weight, bool isDirected) {
    if (isDirected) {
      // For directed graph, only one entry is added for the source to
      // destination
      adjacencyList[source].push_back({destination, weight});
    } else {
      // For undirected graph, entries are added for both source to destination
      // and destination to source
      adjacencyList[source].push_back({destination, weight});
      adjacencyList[destination].push_back({source, weight});
    }
  }

  // Function to print the adjacency list
  void printAdjacencyList() {
    for (auto vertex : adjacencyList) {
      cout << vertex.first << " : ";
      for (auto neighbor : vertex.second) {
        cout << " { " << neighbor.first << " , " << neighbor.second << " } ";
      }
      cout << endl;
    }
  }

  // NOTE: unlike directed graphs here we can't use topological sort to get the
  // element in a dependency order
  // -> so we use min min-heap or set to get the node with the min distance .
  // -> isentially it's bfs only but here we are using set instead of queue .

  // Function to find the shortest paths using Dijkstra's algorithm
  vector<int> dijkstrasShortestPath(int source, int numberOfNodes) {

    // Vector to store the shortest distances from the source to each node
    // NOTE: using unordered_map could be a better / vercitle chaoice
    // -> as we won't need to handle the indexes manually then
    vector<int> shortestDistances(numberOfNodes + 1, INT_MAX);

    // Set to keep track of nodes and their distances, sorted by distance
    // NOTE: using min-heap could be a better chaoice here
    //  -> but then we'll have to write a custom comparator for it .
    set<pair<int, int>> nodesSet;

    // insert a source node into set and mark it's distance as 0
    nodesSet.insert({0, source});
    shortestDistances[source] = 0;

    // Dijkstra's algorithm
    while (!nodesSet.empty()) {

      // Get the node with the smallest distance
      auto currentNode = nodesSet.begin();

      // as a .begin() returns an itirator we need to dereference it with *
      auto currentPair = *(currentNode);

      // get the current node value and it's shortest distance so far
      int currentNodeValue = currentPair.second;
      int currentNodeDistance = currentPair.first;

      // Remove the current node from the set
      nodesSet.erase(nodesSet.begin());

      // Iterate over neighbors of the current node
      for (auto neighborPair : adjacencyList[currentNodeValue]) {

        // take the neighborDistance and neighborNodeValue out of neighborPair
        int neighborNodeValue = neighborPair.first;
        int neighborDistance = neighborPair.second;

        // Update the distance if the currentNodeDistance in addition to the
        // distance to the neighbor from the current node is smaller to the
        // distance of the neighbor.
        if (currentNodeDistance + neighborDistance <
            shortestDistances[neighborNodeValue]) {

          // Remove the previous entry for the neighbor from the set
          auto previousEntry = nodesSet.find(
              {shortestDistances[neighborNodeValue], neighborNodeValue});

          // if the previous entry of a neighbor pair is found erase it
          if (previousEntry != nodesSet.end()) {
            nodesSet.erase(previousEntry);
          }

          // Update the shortest distance for the neighbor node
          shortestDistances[neighborNodeValue] =
              currentNodeDistance + neighborDistance;

          // insert the updated entry back into the set for further processing
          // NOTE: we are inserting this updated neighbor into the set to
          // process it's neighbors and recalculate their distances accordingly.
          nodesSet.insert(
              {shortestDistances[neighborNodeValue], neighborNodeValue});
        }
      }
    }
    return shortestDistances;
  }
};

int main() {

  // Create a directed weighted graph object
  DirectedWeightedGraph weightedGraph;

  // Add edges to the graph
  weightedGraph.insertEdge(1, 6, 14, false);
  weightedGraph.insertEdge(1, 3, 9, false);
  weightedGraph.insertEdge(1, 2, 7, false);
  weightedGraph.insertEdge(2, 3, 10, false);
  weightedGraph.insertEdge(2, 4, 15, false);
  weightedGraph.insertEdge(3, 4, 11, false);
  weightedGraph.insertEdge(6, 3, 2, false);
  weightedGraph.insertEdge(6, 5, 9, false);
  weightedGraph.insertEdge(5, 4, 6, false);

  // Specify the source node and the total number of nodes in the graph
  int sourceNode = 6;
  int totalNodes = 6;

  // Find the shortest paths from the source to all nodes
  vector<int> shortestPaths =
      weightedGraph.dijkstrasShortestPath(sourceNode, totalNodes);

  // Print the adjacency list of the graph
  // weightedGraph.printAdjacencyList();

  // Display the shortest paths from the source to each node
  cout << "Shortest path from " << sourceNode << " to each node is : " << endl;

  // Print the shortest path lengths
  for (int i = 0; i < shortestPaths.size(); i++) {
    cout << i << " -> " << shortestPaths[i] << endl;
  }
  cout << endl;
}

Enter fullscreen mode Exit fullscreen mode

This output indicates the shortest distance from node 6 to all other nodes. Note that 2147483647 (equivalent to INT_MAX) indicates that a node is unreachable from the source.

Key Notes:

  • Set vs Min-Heap: While the code uses a set to get the minimum distance node, a min-heap (priority queue) could be more efficient, albeit with additional complexity.
  • Handling Large Graphs: The algorithm works well for graphs without negative weights but may need adjustments for larger or more complex graphs, such as using a priority queue.

Time Complexity:

  • Dijkstra's Algorithm: O((V + E) log V) where V is the number of vertices and E is the number of edges.

Space Complexity:

  • Dijkstra's Algorithm: O(V + E) for storing the graph, distances, and the set.
. . . . . .
Terabox Video Player