From 2c40cb71d904279d548e26adc5a7b552adb6e38c Mon Sep 17 00:00:00 2001 From: Amrita Goswami Date: Tue, 18 Jun 2024 15:32:22 +0000 Subject: [PATCH] network_operations: Added BFS Added a standard BFS implementation that can find one shortest path, given a source and destination. TODO: test whether the maximum depth works for the BFS algorithm. --- graph_lib/include/network_operations.hpp | 109 ++++++++++++++++++----- test/test_network_operations.cpp | 42 +++++---- 2 files changed, 113 insertions(+), 38 deletions(-) diff --git a/graph_lib/include/network_operations.hpp b/graph_lib/include/network_operations.hpp index 375373e..4e3a46a 100644 --- a/graph_lib/include/network_operations.hpp +++ b/graph_lib/include/network_operations.hpp @@ -1,5 +1,8 @@ #include "network_base.hpp" +#include #include +#include +#include #include namespace Graph { @@ -14,41 +17,52 @@ template class NetworkOperations { NetworkOperations(const NetworkBase &network) : network(network), marked(std::vector(network.n_agents(), false)), - count(0), edge_to_vertex(std::vector(network.n_agents())) {} + edge_to_vertex(std::vector(network.n_agents())), + depth(std::vector(network.n_agents(), INT_MAX)) {} // Returns a path from a source s to a vertex v using DFS (if it exists) - // If it does not exist, return nullopt + // If it does not exist, returns nullopt // This will not give all the paths (or even the shortest path) std::optional> path_from_dfs(size_t s, size_t v) { std::vector path{}; // Path from s to v - reset_variables_counters(); // Reset marked and count + reset_variables_counters(); // Reset marked and depth // DFS starting from the source dfs(s); - if (!path_exists_to_vertex(v)) { - return std::nullopt; // the path does not exist - } - // If the path exists, return it - // Start from v - for (int x = v; x != s; x = edge_to_vertex[x]) { - path.push_back(x); - } - path.push_back(s); // Finally, add the source - return path; + // Get one path if it exists + return path_to_vertex(s, v); + } + + // Returns one (of possibly many) shortest paths from a source s to a vertex v + // using BFS (if it exists) If a shortest path, within an optional depth + // cutoff (inclusive) does not exist, returns nullopt This will not give all + // the shortest paths + std::optional> + shortest_path_from_bfs(size_t s, size_t v, + std::optional max_depth = std::nullopt) { + std::vector path{}; // One shortest path from s to v + reset_variables_counters(); // Reset marked and depth + // BFS starting from the source + bfs(s, max_depth); + // Get one shortest path if it exists + return path_to_vertex(s, v); } + // Get the depth of vertices or distances from the source + // Using this function only makes sense if you have used BFS + std::vector get_depth_from_source() { return depth; } + private: const NetworkBase &network; // UndirectedNetwork or DirectedNetwork - // Chronicles whether a vertex has been visited or not for DFS - // For BFS: chronicles whether a shortest path to this vertex is known - std::vector marked{}; - size_t count{}; // Number of vertices visited + // Chronicles whether a vertex has been visited or not + std::vector marked{}; + std::vector + depth{}; // Distance of the node from the source (given by BFS) std::vector edge_to_vertex{}; // Last vertex on known path, to a vertex // (given by the index in this vector) - // Depth first search from vertex v + // Depth-first search from vertex v void dfs(size_t v) { - count++; // Update the number of vertices visited marked[v] = true; for (size_t w : network.get_neighbours(v)) { if (!marked[w]) { @@ -58,14 +72,65 @@ template class NetworkOperations { } } - // Reset marked (that chronicles visited vertices) and count (number of - // vertices visited) + // Breadth-first search from a source node/vertex s + void bfs(size_t s, std::optional max_depth = std::nullopt) { + std::queue q; // To keep track of vertices to visit + depth[s] = 0; // Distance from the source + // Mark the source and put it on the queue + marked[s] = true; + q.push(s); + + while (!q.empty()) { + // Remove next vertex from the queue + size_t v = q.front(); + q.pop(); + // Check to see if the maximum depth has been reached, break if it has + // been reached + if (max_depth.has_value()) { + if (depth[v] > max_depth) { + break; // Stop BFS if the maximum search depth has been reached + } + } + + // Go through the neighbours of v + for (size_t w : network.get_neighbours(v)) { + // Process every unmarked adjacent vertex + // All unmarked vertices that are adjacent to v are added to the queue + if (!marked[w]) { + edge_to_vertex[w] = v; // Save last edge on *a* shortest path + marked[w] = true; // Mark it because the path is known + depth[w] = depth[v] + 1; // Update the distance from the source for w + q.push(w); // Add it to the queue + } + } + } + } + + // Reset marked (that chronicles visited vertices) and depth (distance of the + // vertex from the source) void reset_variables_counters() { - count = 0; + depth.clear(); + depth.resize(network.n_agents(), INT_MAX); + marked.clear(); marked.resize(network.n_agents(), false); } // Returns false if a path to vertex v does not exist bool path_exists_to_vertex(size_t v) { return marked[v]; } + + // Returns one path from s to v (shortest if BFS was used) + std::optional> path_to_vertex(size_t s, size_t v) { + if (!path_exists_to_vertex(v)) { + return std::nullopt; // the path does not exist + } + // If the path exists, return it + // Start from v + std::vector path{}; // Path from s to v + for (size_t x = v; x != s; x = edge_to_vertex[x]) { + path.push_back(x); + } + path.push_back(s); // Finally, add the source + return path; + } }; } // namespace Graph diff --git a/test/test_network_operations.cpp b/test/test_network_operations.cpp index acf33b6..88fe5ca 100644 --- a/test/test_network_operations.cpp +++ b/test/test_network_operations.cpp @@ -10,26 +10,26 @@ #include #include -TEST_CASE("Testing network operations") { +TEST_CASE("Testing bog-standard DFS and BFS") { using namespace Graph; using UndirectedNetwork = UndirectedNetwork; using WeightT = double; // Generate some network -/* Nodes: 0, 2, 1, 3, 4, 5 -ACII art made in https://asciiflow.com - +-+ - |0| - +-+ - +-+ - |2| -+---------+ -|1| |3| -+-+ +-+ - +--+ +---+ - |4| |5| - +-+ +-+ -*/ + /* Nodes: 0, 2, 1, 3, 4, 5 + ACII art made in https://asciiflow.com + +-+ + |0| + +-+ + +-+ + |2| + +---------+ + |1| |3| + +-+ +-+ + +--+ +---+ + |4| |5| + +-+ +-+ + */ const auto n_agents = 6; auto network = UndirectedNetwork(n_agents); const WeightT weight_edge = 1.0; @@ -49,10 +49,20 @@ ACII art made in https://asciiflow.com // Create a graph traversal object auto graph_traversal = NetworkOperations(network); - // Find a path from 0 to 5 + // Find a path from 0 to 5 using DFS const auto path_ref = std::vector{0, 2, 3, 5}; // Desired path auto path = graph_traversal.path_from_dfs(0, 5); INFO(fmt::format("path from DFS = {}\n", path.value())); REQUIRE_THAT(path.value(), Catch::Matchers::UnorderedRangeEquals(path_ref)); + + // Find a path from 0 to 5 using BFS (no maximum depth) + path = graph_traversal.shortest_path_from_bfs(0, 5); + INFO(fmt::format("path from BFS = {}\n", path.value())); + REQUIRE_THAT(path.value(), Catch::Matchers::UnorderedRangeEquals(path_ref)); + // Check that the distance from the source is correct, when using BFS + const auto depth_required = std::vector{0, 2, 1, 2, 3, 3}; + auto depth_bfs = graph_traversal.get_depth_from_source(); + INFO(fmt::format("Distances of vertices from the source = {}\n", depth_bfs)); + REQUIRE_THAT(depth_bfs, Catch::Matchers::RangeEquals(depth_required)); } \ No newline at end of file