Skip to content

Commit

Permalink
network_operations: Added BFS
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
amritagos committed Jun 18, 2024
1 parent 3c9455e commit 2c40cb7
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 38 deletions.
109 changes: 87 additions & 22 deletions graph_lib/include/network_operations.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "network_base.hpp"
#include <climits>
#include <cstddef>
#include <optional>
#include <queue>
#include <vector>

namespace Graph {
Expand All @@ -14,41 +17,52 @@ template <typename WeightType = double> class NetworkOperations {

NetworkOperations(const NetworkBase<WeightT> &network)
: network(network), marked(std::vector<bool>(network.n_agents(), false)),
count(0), edge_to_vertex(std::vector<size_t>(network.n_agents())) {}
edge_to_vertex(std::vector<size_t>(network.n_agents())),
depth(std::vector<int>(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<std::vector<size_t>> path_from_dfs(size_t s, size_t v) {
std::vector<size_t> 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<std::vector<size_t>>
shortest_path_from_bfs(size_t s, size_t v,
std::optional<int> max_depth = std::nullopt) {
std::vector<size_t> 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<int> get_depth_from_source() { return depth; }

private:
const NetworkBase<WeightT> &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<bool> marked{};
size_t count{}; // Number of vertices visited
// Chronicles whether a vertex has been visited or not
std::vector<bool> marked{};
std::vector<int>
depth{}; // Distance of the node from the source (given by BFS)
std::vector<size_t>
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]) {
Expand All @@ -58,14 +72,65 @@ template <typename WeightType = double> 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<int> max_depth = std::nullopt) {
std::queue<size_t> 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<std::vector<size_t>> 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<size_t> 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
42 changes: 26 additions & 16 deletions test/test_network_operations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,26 @@
#include <set>
#include <vector>

TEST_CASE("Testing network operations") {
TEST_CASE("Testing bog-standard DFS and BFS") {
using namespace Graph;
using UndirectedNetwork = UndirectedNetwork<double>;
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;
Expand All @@ -49,10 +49,20 @@ ACII art made in https://asciiflow.com
// Create a graph traversal object
auto graph_traversal = NetworkOperations<WeightT>(network);

// Find a path from 0 to 5
// Find a path from 0 to 5 using DFS
const auto path_ref = std::vector<size_t>{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<int>{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));
}

0 comments on commit 2c40cb7

Please sign in to comment.