From 2a36de7c527d50c0c2a6611985702591395d56ba Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Mon, 12 Jul 2021 14:25:43 -0400 Subject: [PATCH] Add pathing using BFS within the simple-graph example --- cpp/algorithms/graphs/simple/graph.cpp | 42 +++++++++++++++------- cpp/algorithms/graphs/simple/lib-graph.cpp | 37 +++++++++++++++++++ cpp/algorithms/graphs/simple/lib-graph.hpp | 7 ++-- 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/cpp/algorithms/graphs/simple/graph.cpp b/cpp/algorithms/graphs/simple/graph.cpp index 9ed2e30..2c7ebc8 100644 --- a/cpp/algorithms/graphs/simple/graph.cpp +++ b/cpp/algorithms/graphs/simple/graph.cpp @@ -13,6 +13,9 @@ int main (const int argc, const char * argv[]) { // We could initialize the graph with some localNodes... + // This graph uses an unordered_(map/set), so initialization is reversed + // + So the final order of initialization is 1,2,3,4,5,6,7,8 + // + Similarly, adjacent nodes are inserted at front (6,4 initializes to 4,6) std::unordered_map> localNodes{ {8, {6, 4}}, {7, {8, 6, 4, 3}}, @@ -45,6 +48,17 @@ int main (const int argc, const char * argv[]) // + Chapter 22, Figure 22.3 on BFS bfsGraph.BFS(2); + std::cout << "\nTesting finding a path between two nodes using BFS...\n"; + auto path = bfsGraph.PathBFS(1, 7); + if (path.empty()) std::cout << "No valid path found!\n"; + else { + std::cout << "\nValid path from " << path.front() << " to " + << path.back() << ": "; + for (const auto &node : path) { + std::cout << node << " "; + } + std::cout << std::endl; + } std::cout << "\n\n##### Depth First Search #####\n"; // Initialize an example graph for Depth First Search @@ -64,18 +78,22 @@ int main (const int argc, const char * argv[]) std::cout << "\n\n##### Topological Sort #####\n"; + // The graph traversed in this example is seen in MIT Intro to Algorithms + // + Chapter 22, Figure 22.4 on DFS // Initialize an example graph for Topological Sort + // + The final result will place node 3 (watch) at the beginning of the order + // + This is because node 3 has no connecting node Graph topologicalGraph ( { - {9, {}}, - {8, {9}}, - {7, {9}}, - {6, {7, 8}}, - {5, {}}, - {4, {7, 5}}, - {3, {}}, - {2, {5}}, - {1, {5, 4}}, + {9, {}}, // jacket + {8, {9}}, // tie + {7, {9}}, // belt + {6, {7, 8}}, // shirt + {5, {}}, // shoes + {4, {7, 5}}, // pants + {3, {}}, // watch + {2, {5}}, // socks + {1, {5, 4}}, // undershorts } ); auto order = topologicalGraph.TopologicalSort(topologicalGraph.GetNode(6)); @@ -86,9 +104,9 @@ int main (const int argc, const char * argv[]) } std::cout << std::endl << std::endl; - // If we want the topological order to match what is seen in the book + // If we want the topological order to exactly match what is seen in the book // + We have to initialize the graph carefully to get this result - - // Because this is an unordered_(map/set) initialization is reversed + // This graph uses an unordered_(map/set), so initialization is reversed // + So the order of nodes on the container below is 6,7,8,9,3,1,4,5,2 // + The same concept applies to their adjacent nodes (7,8 initializes to 8,7) // + In object-graph implementation, I use vectors this does not apply there @@ -122,7 +140,5 @@ int main (const int argc, const char * argv[]) } std::cout << std::endl; - std::cout << std::endl; - return 0; } diff --git a/cpp/algorithms/graphs/simple/lib-graph.cpp b/cpp/algorithms/graphs/simple/lib-graph.cpp index 67b49e9..fb43369 100644 --- a/cpp/algorithms/graphs/simple/lib-graph.cpp +++ b/cpp/algorithms/graphs/simple/lib-graph.cpp @@ -16,6 +16,9 @@ void Graph::BFS(int startNode) { // Track the nodes we have discovered std::vector discovered(nodes_.size(), false); + // Reset values of predecessor and distance JIC there was a previous traversal + for (auto &p : predecessor) p = std::make_pair(0, INT32_MIN); + for (auto &d : distance) d = std::make_pair(0, 0); // Create a queue to visit discovered nodes in FIFO order std::queue visitQueue; @@ -37,6 +40,14 @@ void Graph::BFS(int startNode) for (const auto &adjacent : nodes_[thisNode]) { if (!discovered[adjacent - 1]) { std::cout << "Found undiscovered adjacentNode: " << adjacent << "\n"; + + // Update the distance from the start node + distance[adjacent - 1] = + std::make_pair(adjacent, distance[thisNode - 1].second + 1); + // Update the predecessor for the adjacent node when we discover it + // + The node that first discovers the adjacent is the predecessor + predecessor[adjacent - 1] = std::make_pair(adjacent, thisNode); + // Mark the adjacent node as discovered // + If this were done out of the for loop we could discover nodes twice // + This would result in visiting the node twice, since it appears @@ -52,6 +63,32 @@ void Graph::BFS(int startNode) } +std::deque Graph::PathBFS(int start, int finish) +{ + // Store the path as a deque of integers so we can push to the front and back + std::deque path; + // Perform BFS on the start node, updating all possible predecessors + BFS(start); + // Begin at the finish node's predecessor + int next = predecessor[finish - 1].second; + bool isValid = false; + do { + // If the next node is the start node, we have found a valid path + if (next == start) isValid = true; + // Add the next node to the path + path.push_front(next); + // Move to the predecessor of the next node + next = predecessor[next - 1].second; + } while (next != INT32_MIN); // If we hit a node with no predecessor, break + // Push the finish node the end of the path + // + We could do this prior to the loop with push_front.. but, deques :) + path.push_back(finish); + // If we never found a valid path, erase the path + if (!isValid) path.erase(path.begin(), path.end()); + // Return the path, the caller should handle the case where the path is empty + return path; +} + void Graph::DFS() { // Track the nodes we have discovered diff --git a/cpp/algorithms/graphs/simple/lib-graph.hpp b/cpp/algorithms/graphs/simple/lib-graph.hpp index a0c692f..ec445fc 100644 --- a/cpp/algorithms/graphs/simple/lib-graph.hpp +++ b/cpp/algorithms/graphs/simple/lib-graph.hpp @@ -11,9 +11,7 @@ #define LIB_GRAPH_HPP #include -#include #include -#include #include #include #include @@ -26,9 +24,12 @@ public: { discoveryTime.resize(nodes_.size()); finishTime.resize(nodes_.size(), std::make_pair(0,0)); + predecessor.resize(nodes_.size(), std::make_pair(0, INT32_MIN)); + distance.resize(nodes_.size(), std::make_pair(0, 0)); } void BFS(int startNode); + std::deque PathBFS(int start, int finish); void DFS(); void DFS(Node::iterator startNode); @@ -52,6 +53,8 @@ private: // Unordered to avoid container reorganizing elements // + Since this would alter the order nodes are traversed in Node nodes_; + std::vector> distance; + std::vector> predecessor; // Where the first element in the following two pairs is the node number // And the second element is the discovery / finish time