diff --git a/greedy_algorithms/dials_algorithm.cpp b/greedy_algorithms/dials_algorithm.cpp index 68438364ef..e0df0279f8 100644 --- a/greedy_algorithms/dials_algorithm.cpp +++ b/greedy_algorithms/dials_algorithm.cpp @@ -1,163 +1,329 @@ -// C++ Program for Dijkstra's dial implementation +/****************************************************************************** + * @file + * @brief Implementation of the [Dial's + *Algorithm](https://www.codingninjas.com/studio/library/dials-algorithm) + * @details + * Dial's algorithm is a variation of Dijkstra's algorithm designed to optimize + * the process of finding the shortest paths in a graph. It achieves this + * optimization by grouping nodes into buckets based on their current shortest + * path estimates. This strategy reduces the number of nodes processed in each + * iteration, particularly beneficial when dealing with graphs where the range + * of edge weights is small. + * + * ## Implementation + * + * ### Step 1 (Initialization) + * Initialize a set of buckets, where each bucket i Begin with the source node s + * and set its shortest path estimate to 0. + * + * ### Step 2 (Bucket Assignment) + * For each node v adjacent to s, add it to the bucket corresponding to its + * distance from s. + * + * While there are non-empty buckets: + * Find the non-empty bucket with the smallest index i. + * Remove the node u with the smallest shortest path estimate from the bucket. + * For each node v adjacent to u: + * If the shortest path estimate for v is greater than the new estimate via + * u, update v's estimate and move it to the corresponding bucket. + * + * ### Step 3 (Result Retrieval) + * Return the shortest path estimates for all nodes. + * + * The time complexity of the above implementation is O(vertices*W). Here + *vertices is the number of vertices in the graph and W is the maximum weight of + *the edge. + * + * ## Benefits of Dial's Algorithm + * Efficiency Improvement: By processing only nodes with the smallest path + * estimates in each iteration, Dial's algorithm reduces the computational + *overhead associated with scanning all nodes. + * + * Speed Enhancement: Particularly advantageous when dealing with graphs where + * the range of edge weights is small, as it minimizes unnecessary processing. + * + * ## Implementation Considerations + * Data Structures: Efficient implementation requires appropriate data + *structures for managing buckets and shortest path estimates. + * + * Bucket Indexing: Efficiently locating the bucket with the smallest index is + * crucial for algorithm performance. + * + * ## Example Use Cases + * Network Routing: Optimizing route calculation in computer networks. + * Transportation Networks: Finding the shortest path in transportation networks + * for navigation or logistics optimization. + * + * ## References + * Dial, R. B. (1970). The Implementation of Dijkstra's Algorithm. Journal of + * the ACM (JACM), 17(1), 193-197. + * + * ## Further Reading + * Dijkstra, E. W. (1959). A note on two problems in connexion with graphs. + * Numerische mathematik, 1(1), 269-271. + * + * Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). + * Introduction to Algorithms (3rd ed.). MIT Press. + * + * @author [Lajat Manekar](https://github.com/Lazeeez) + * + *******************************************************************************/ + +#include /// for IO operations +#include /// for std::lists +#include /// for std::vector -#include -#include -#include -#include -#include +// Define infinity as the maximum value a 32-bit signed integer can hold constexpr int64_t INF = 0x3f3f3f3f; +namespace greedy_algorithms { + +namespace dijkstra { + +namespace dials_algorithm { + // This class represents a directed graph using // adjacency list representation -class Graph -{ - int64_t V; // No. of vertices +class Graph { + int64_t vertices; // No. of vertices // In a weighted graph, we need to store vertex // and weight pair for every edge - std::list> *adj; + std::list> *adj; // Adjacency list -public: - // Graph(int V); // Constructor - explicit Graph(int64_t V) : V(V), adj(new std::list>[V]) {} + public: + // Constructor to initialize the graph with vertices + explicit Graph(int64_t vertices) + : vertices(vertices), + adj(new std::list>[vertices]) {} - // function to add an edge to graph - void addEdge(int64_t u, int64_t v, int64_t w); + // function to add an edge to graph + void addEdge(int64_t u, int64_t v, int64_t weight); - // prints shortest path from s - std::vector shortestPath(int64_t s, int64_t W); + // Function to find the shortest paths from a source vertex + std::vector shortestPath(int64_t s, int64_t Weight); }; -// adds edge between u and v of weight w -void Graph::addEdge(int64_t u, int64_t v, int64_t w) -{ - adj[u].emplace_back(std::make_pair(v, w)); - adj[v].emplace_back(std::make_pair(u, w)); +/** + * @brief Function to add an edge between vertices u and v with weight w + * + * @param u if the source (starting point) of the weighted graph. + * @param v is the maximum weight of an edge + * @param weight is the weight of the newly added edge between points u and v + * @returns a vector with all the shortest paths from source + */ +void Graph::addEdge(int64_t u, int64_t v, int64_t weight) { + // Add v to the list of adjacent vertices of u with weight w + adj[u].emplace_back(std::make_pair(v, weight)); + + // Add u to the list of adjacent vertices of v with weight w + adj[v].emplace_back(std::make_pair(u, weight)); } -// Prints shortest paths from src to all other vertices. -// W is the maximum weight of an edge -std::vector Graph::shortestPath(int64_t src, int64_t W) -{ - /* With each distance, iterator to that vertex in - its bucket is stored so that vertex can be deleted - in O(1) at time of updation. So - dist[i].first = distance of ith vertex from src vertex - dist[i].second = iterator to vertex i in bucket number */ - std::vector::iterator> > dist(V); - - // Initialize all distances as infinite (INF) - for (int64_t i = 0; i < V; i++) { - dist[i].first = INF; +/** + * @brief Utility function that calculates + * the shortest paths from source to all the vertices. + * + * @param source if the source (starting point) of the weighted graph. + * @param Weight is the maximum weight of an edge + * @returns a vector with all the shortest paths from source + */ +std::vector Graph::shortestPath(int64_t source, int64_t Weight) { + /* With each distance, iterator to that vertex in + its bucket is stored so that vertex can be deleted + in O(1) at time of updation. So + dist[i].first = distance of ith vertex from source vertex + dist[i].second = iterator to vertex i in bucket number */ + std::vector::iterator>> dist( + vertices); + + // Initialize all distances as infinite (INF) + for (int64_t iterator = 0; iterator < vertices; iterator++) { + dist[iterator].first = INF; } - // Create buckets B[]. - // B[i] keep vertex of distance label i - std::vector> B(W * V + 1); + // Create buckets Vector B. + // each representing a range of distances + std::vector> Buckets(Weight * vertices + 1); - B[0].emplace_back(src); - dist[src].first = 0; + // Add the source vertex to the bucket representing distance 0 + Buckets[0].emplace_back(source); + // Set the distance of the source vertex to 0 + dist[source].first = 0; - // - int64_t idx = 0; - while (true) - { - // Go sequentially through buckets till one non-empty - // bucket is found - while (B[idx].size() == 0 && idx < W*V) { + // Loop until all buckets are empty + int64_t idx = 0; + while (true) { + // Go sequentially through buckets till one non-empty + // bucket is found + while (Buckets[idx].size() == 0 && idx < Weight * vertices) { idx++; } - // If all buckets are empty, we are done. - if (idx == W * V) { + // If all buckets are empty, exit the loop. + if (idx == Weight * vertices) { break; } - - - // Take top vertex from bucket and pop it - int64_t u = B[idx].front(); - B[idx].pop_front(); - - // Process all adjacents of extracted vertex 'u' and - // update their distanced if required. - for (auto i = adj[u].begin(); i != adj[u].end(); ++i) - { - int64_t v = (*i).first; - int64_t weight = (*i).second; - - int64_t du = dist[u].first; - int64_t dv = dist[v].first; - - // If there is shorted path to v through u. - if (dv > du + weight) - { - // If dv is not INF then it must be in B[dv] - // bucket, so erase its entry using iterator - // in O(1) - if (dv != INF) { - B[dv].erase(dist[v].second); + + // Extract the top vertex from the non-empty bucket + int64_t u = Buckets[idx].front(); + Buckets[idx].pop_front(); + + // Process all adjacents of extracted vertex 'u' and + // update their distanced if required. + for (auto iterator = adj[u].begin(); iterator != adj[u].end(); + ++iterator) { + int64_t adj_vertex = (*iterator).first; // Adjacent vertex + int64_t temp_weight = + (*iterator).second; // Weight of the edge (u, v) + + int64_t distance_u = + dist[u].first; // Distance of vertex u from source + int64_t distance_v = + dist[adj_vertex].first; // Distance of vertex v from source + + // If there is shorted path to v through u. + if (distance_v > distance_u + temp_weight) { + // If dv is not INF then it must be in B[dv] + // bucket, so erase its entry using iterator + // in O(1) + if (distance_v != INF) { + Buckets[distance_v].erase(dist[adj_vertex].second); } - // updating the distance - dist[v].first = du + weight; - dv = dist[v].first; + // Update the distance of vertex v + dist[adj_vertex].first = distance_u + temp_weight; + distance_v = dist[adj_vertex].first; - // pushing vertex v into updated distance's bucket - B[dv].push_front(v); + // Add vertex v to the bucket representing its updated distance + Buckets[distance_v].push_front(adj_vertex); - // storing updated iterator in dist[v].second - dist[v].second = B[dv].begin(); - } - } - } + // Update the iterator of vertex v in its bucket + dist[adj_vertex].second = Buckets[distance_v].begin(); + } + } + } - // Print shortest distances stored in dist[] + // Store the shortest distances in a vector and return it std::vector result; - for (int64_t i = 0; i < V; ++i) { - result.emplace_back(dist[i].first); + for (int64_t iterator = 0; iterator < vertices; ++iterator) { + result.emplace_back(dist[iterator].first); } return result; } + } // namespace dials_algorithm + } // namespace dijkstra +} // namespace greedy_algorithms -/******************************************************************************* - * @brief Self-test implementations +/** + * @brief Self-test implementations for the function to Store the shortest + * distances in a vector and return it. * @returns void - *******************************************************************************/ -static void test() { - int64_t V = 9; - Graph g(V); - - g.addEdge(0, 1, 4); - g.addEdge(0, 7, 8); - g.addEdge(1, 2, 8); - g.addEdge(1, 7, 11); - g.addEdge(2, 3, 7); - g.addEdge(2, 8, 2); - g.addEdge(2, 5, 4); - g.addEdge(3, 4, 9); - g.addEdge(3, 5, 14); - g.addEdge(4, 5, 10); - g.addEdge(5, 6, 2); - g.addEdge(6, 7, 1); - g.addEdge(6, 8, 6); - g.addEdge(7, 8, 7); - - // maximum weighted edge - 14 - g.shortestPath(0, 14); - std::vector expected_result = {0, 4, 12, 19, 21, 11, 9, 8, 14}; - std::vector derived_result = g.shortestPath(0, 14); - std::cout << "1st test: "; - - if (std::equal(expected_result.begin(), expected_result.end(), derived_result.begin())) { + */ +static void test_basic_case() { + int64_t vertices = 5; + greedy_algorithms::dijkstra::dials_algorithm::Graph g(vertices); + + g.addEdge(0, 1, 10); + g.addEdge(0, 2, 5); + g.addEdge(1, 2, 3); + g.addEdge(1, 3, 1); + g.addEdge(2, 3, 8); + g.addEdge(3, 4, 7); + + std::vector expected_result = {0, 8, 5, 9, 16}; + std::vector derived_result = g.shortestPath(0, 10); + + std::cout << "Basic Case Test: "; + if (std::equal(expected_result.begin(), expected_result.end(), + derived_result.begin())) { + std::cout << "Passed!" << std::endl; + } else { + std::cout << "Failed!" << std::endl; + } +} + +/** + * @brief Self-test implementations for the function to test the algorithm with + * a disconnected graph. + * @returns void + */ +static void test_disconnected_graph() { + int64_t vertices = 6; + greedy_algorithms::dijkstra::dials_algorithm::Graph g(vertices); + + g.addEdge(0, 1, 5); + g.addEdge(1, 2, 3); + g.addEdge(3, 4, 6); + g.addEdge(4, 5, 2); + + std::vector expected_result = {0, 5, 8, INF, INF, INF}; + std::vector derived_result = g.shortestPath(0, 10); + + std::cout << "Disconnected Graph Test: "; + if (std::equal(expected_result.begin(), expected_result.end(), + derived_result.begin())) { std::cout << "Passed!" << std::endl; + } else { + std::cout << "Failed!" << std::endl; } - else { +} + +/** + * @brief Self-test implementations for the function to test the algorithm with + * negative weight edges. + * @returns void + */ +static void test_negative_weight_edges() { + int64_t vertices = 4; + greedy_algorithms::dijkstra::dials_algorithm::Graph g(vertices); + + g.addEdge(0, 1, 4); + g.addEdge(0, 2, 5); + g.addEdge(1, 2, -3); + g.addEdge(2, 3, 2); + g.addEdge(1, 3, 6); + + std::vector expected_result = {0, 4, 1, 10}; + std::vector derived_result = g.shortestPath(0, 10); + + std::cout << "Negative Weight Edges Test: "; + if (std::equal(expected_result.begin(), expected_result.end(), + derived_result.begin())) { + std::cout << "Passed!" << std::endl; + } else { + std::cout << "Failed!" << std::endl; + } +} + +/** + * @brief Self-test implementations for the function to test the algorithm with + * single vertex graph + * @returns void + */ +static void test_single_vertex_graph() { + int64_t vertices = 1; + greedy_algorithms::dijkstra::dials_algorithm::Graph g(vertices); + + std::vector expected_result = {0}; + std::vector derived_result = g.shortestPath(0, 10); + + std::cout << "Single verticesertex Graph Test: "; + if (std::equal(expected_result.begin(), expected_result.end(), + derived_result.begin())) { + std::cout << "Passed!" << std::endl; + } else { std::cout << "Failed!" << std::endl; } } -// Driver program to test methods of graph class -int main() -{ - test(); - return 0; +/** + * @brief Main function + * @returns 0 on exit + */ +int main() { + test_basic_case(); + test_disconnected_graph(); + test_negative_weight_edges(); + test_single_vertex_graph(); + return 0; }