From 64df3419a03868f1e43d9f0c1d13a42c2aa16b0b Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Mon, 12 Jul 2021 16:52:49 -0400 Subject: [PATCH] Add structs to track traversal information in object-graph example + Allows Graph member functions to remain const + Easy to pass traversal information around as needed + Update DFS and BFS functions to return traversal information --- cpp/algorithms/graphs/object/graph.cpp | 1 - cpp/algorithms/graphs/object/lib-graph.cpp | 86 ++++++++++++---------- cpp/algorithms/graphs/object/lib-graph.hpp | 76 ++++++++++--------- 3 files changed, 87 insertions(+), 76 deletions(-) diff --git a/cpp/algorithms/graphs/object/graph.cpp b/cpp/algorithms/graphs/object/graph.cpp index edd6771..990db59 100644 --- a/cpp/algorithms/graphs/object/graph.cpp +++ b/cpp/algorithms/graphs/object/graph.cpp @@ -45,7 +45,6 @@ int main (const int argc, const char * argv[]) // The graph traversed in this example is seen in MIT Intro to Algorithms // + Chapter 22, Figure 22.3 on BFS bfsGraph.BFS(bfsGraph.GetNodeCopy(2)); - Node test = bfsGraph.GetNodeCopy(3); std::cout << "\nTesting finding a path between two nodes using BFS...\n"; // Test finding a path between two nodes using BFS diff --git a/cpp/algorithms/graphs/object/lib-graph.cpp b/cpp/algorithms/graphs/object/lib-graph.cpp index 9979b6a..33849e3 100644 --- a/cpp/algorithms/graphs/object/lib-graph.cpp +++ b/cpp/algorithms/graphs/object/lib-graph.cpp @@ -10,23 +10,16 @@ #include "lib-graph.hpp" -void Graph::BFS(const Node& startNode) const +InfoBFS Graph::BFS(const Node& startNode) const { - // Track the nodes we have discovered by their Color - for (const auto &node : nodes_) { - node.color = White; - // Track distance from the startNode - node.distance = 0; - // Track predecessor using node that discovers this node - // + If this is the startNode, predecessor remains nullptr - node.predecessor = nullptr; - } + // Create local object to track the information gathered during traversal + InfoBFS searchInfo; // Create a queue to visit discovered nodes in FIFO order std::queue visitQueue; // Mark the startNode as in progress until we finish checking adjacent nodes - startNode.color = Gray; + searchInfo[startNode.number].discovered = Gray; // Visit the startNode visitQueue.push(&startNode); @@ -40,20 +33,23 @@ void Graph::BFS(const Node& startNode) const // Check if we have already discovered all the adjacentNodes to thisNode for (const auto &adjacent : thisNode->adjacent) { - if (GetNode(adjacent).color == White) { + if (searchInfo[adjacent].discovered == White) { std::cout << "Found undiscovered adjacentNode: " << adjacent << "\n"; // Mark the adjacent node as in progress - GetNode(adjacent).color = Gray; - GetNode(adjacent).distance = thisNode->distance + 1; - GetNode(adjacent).predecessor = &GetNode(thisNode->number); + searchInfo[adjacent].discovered = Gray; + searchInfo[adjacent].distance = searchInfo[thisNode->number].distance + 1; + searchInfo[adjacent].predecessor = &GetNode(thisNode->number); // Add the discovered node the the visitQueue visitQueue.push(&GetNode(adjacent)); } } // We are finished with this node and the adjacent nodes; Mark it discovered - GetNode(thisNode->number).color = Black; + searchInfo[thisNode->number].discovered = Black; } + + // Return the information gathered from this search, JIC caller needs it + return searchInfo; } std::deque Graph::PathBFS(const Node &start, const Node &finish) const @@ -62,8 +58,8 @@ std::deque Graph::PathBFS(const Node &start, const Node &finish) const // + If the caller modifies these, it will not impact the graph's data std::deque path; - BFS(start); - const Node * next = finish.predecessor; + InfoBFS searchInfo = BFS(start); + const Node * next = searchInfo[finish.number].predecessor; bool isValid = false; do { // If we have reached the start node, we have found a valid path @@ -74,7 +70,7 @@ std::deque Graph::PathBFS(const Node &start, const Node &finish) const path.emplace_front(*next); // Move to the next node - next = next->predecessor; + next = searchInfo[next->number].predecessor; } while (next != nullptr); // Use emplace_back to call Node copy constructor path.emplace_back(finish); @@ -86,29 +82,30 @@ std::deque Graph::PathBFS(const Node &start, const Node &finish) const return path; } -void Graph::DFS() const +InfoDFS Graph::DFS() const { // Track the nodes we have discovered - for (const auto &node : nodes_) node.color = White; + InfoDFS searchInfo; int time = 0; // Visit each node in the graph for (const auto& node : nodes_) { std::cout << "Visiting node " << node.number << std::endl; // If the node is undiscovered, visit it - if (node.color == White) { + if (searchInfo[node.number].discovered == White) { std::cout << "Found undiscovered node: " << node.number << std::endl; // Visiting the undiscovered node will check it's adjacent nodes - DFSVisit(time, node); + DFSVisit(time, node, searchInfo); } } + return searchInfo; } -void Graph::DFS(const Node &startNode) const +InfoDFS Graph::DFS(const Node &startNode) const { // Track the nodes we have discovered - for (const auto &node : nodes_) node.color = White; + InfoDFS searchInfo; int time = 0; auto startIter = std::find(nodes_.begin(), nodes_.end(), @@ -119,10 +116,10 @@ void Graph::DFS(const Node &startNode) const while (startIter != nodes_.end()) { std::cout << "Visiting node " << startIter->number << std::endl; // If the startIter is undiscovered, visit it - if (startIter->color == White) { + if (searchInfo[startIter->number].discovered == White) { std::cout << "Found undiscovered node: " << startIter->number << std::endl; // Visiting the undiscovered node will check it's adjacent nodes - DFSVisit(time, *startIter); + DFSVisit(time, *startIter, searchInfo); } startIter++; } @@ -134,20 +131,22 @@ void Graph::DFS(const Node &startNode) const while (*startIter != startNode) { std::cout << "Visiting node " << startIter->number << std::endl; // If the startIter is undiscovered, visit it - if (startIter->color == White) { + if (searchInfo[startIter->number].discovered == White) { std::cout << "Found undiscovered node: " << startIter->number << std::endl; // Visiting the undiscovered node will check it's adjacent nodes - DFSVisit(time, *startIter); + DFSVisit(time, *startIter, searchInfo); } startIter++; } + + return searchInfo; } -void Graph::DFSVisit(int &time, const Node& startNode) const +void Graph::DFSVisit(int &time, const Node& startNode, InfoDFS &searchInfo) const { - startNode.color = Gray; + searchInfo[startNode.number].discovered = Gray; time++; - startNode.discoveryFinish.first = time; + searchInfo[startNode.number].discoveryFinish.first = time; // Check the adjacent nodes of the startNode for (const auto &adjacent : startNode.adjacent) { @@ -155,26 +154,33 @@ void Graph::DFSVisit(int &time, const Node& startNode) const Node(adjacent, {})); // If the adjacentNode is undiscovered, visit it // + Offset by 1 to account for 0 index of discovered vector - if (iter->color == White) { + if (searchInfo[iter->number].discovered == White) { std::cout << "Found undiscovered adjacentNode: " << GetNode(adjacent).number << std::endl; // Visiting the undiscovered node will check it's adjacent nodes - DFSVisit(time, *iter); + DFSVisit(time, *iter, searchInfo); } } - startNode.color = Black; + searchInfo[startNode.number].discovered = Black; time++; - startNode.discoveryFinish.second = time; + searchInfo[startNode.number].discoveryFinish.second = time; } std::vector Graph::TopologicalSort(const Node &startNode) const { - DFS(GetNode(startNode.number)); - std::vector topological(nodes_); + InfoDFS topological = DFS(GetNode(startNode.number)); - std::sort(topological.begin(), topological.end(), Node::FinishedSort); + std::vector order(nodes_); + + auto comp = [&topological](const Node &a, const Node &b) { + return (topological[a.number].discoveryFinish.second < + topological[b.number].discoveryFinish.second); + }; + + std::sort(order.begin(), order.end(), comp); // The topologicalOrder is read right-to-left in the final result // + Output is handled in main as FILO, similar to a stack - return topological; + return order; } + diff --git a/cpp/algorithms/graphs/object/lib-graph.hpp b/cpp/algorithms/graphs/object/lib-graph.hpp index d9d0f92..7246583 100644 --- a/cpp/algorithms/graphs/object/lib-graph.hpp +++ b/cpp/algorithms/graphs/object/lib-graph.hpp @@ -17,11 +17,46 @@ #include #include #include +#include + +/******************************************************************************/ +// Structures for tracking information gathered from various traversals +struct Node; // Color represents the discovery status of any given node // + White is undiscovered, Gray is in progress, Black is fully discovered enum Color {White, Gray, Black}; +// Information used in all searches +struct SearchInfo { + // Coloring of the nodes is used in both DFS and BFS + Color discovered = White; +}; + +// Information that is only used in BFS +struct BFS : SearchInfo { + // Used to represent distance from start node + int distance = 0; + // Used to represent the parent node that discovered this node + // + If we use this node as the starting point, this will remain a nullptr + const Node *predecessor = nullptr; +}; + +// Information that is only used in DFS +struct DFS : SearchInfo { + // Create a pair to track discovery / finish time + // + Discovery time is the iteration the node is first discovered + // + Finish time is the iteration the node has been checked completely + // ++ A finished node has considered all adjacent nodes + std::pair discoveryFinish; +}; + +// Store search information in unordered_maps so we can pass it around easily +// + Allows each node to store relative information on the traversal +using InfoBFS = std::unordered_map; +using InfoDFS = std::unordered_map; + + /******************************************************************************/ // Node structure for representing a graph struct Node { @@ -38,37 +73,11 @@ public: friend void swap(Node &a, Node &b) { std::swap(a.number, b.number); std::swap(a.adjacent, b.adjacent); - std::swap(a.color, b.color); - std::swap(a.discoveryFinish, b.discoveryFinish); } - // Don't allow anyone to change these values when using a const reference int number; std::vector adjacent; - // Mutable members so we can update these values when using a const reference - // + Since they need to be modified during traversals - - // Coloring of the nodes are used in both DFS and BFS - mutable Color color = White; - - // Used in BFS to represent distance from start node - mutable int distance = 0; - // Used in BFS to represent the parent node that discovered this node - // + If we use this node as the starting point, this will remain a nullptr - mutable const Node *predecessor = nullptr; - - // Create a pair to track discovery / finish time when using DFS - // + Discovery time is the iteration the node is first discovered - // + Finish time is the iteration the node has been checked completely - // ++ A finished node has considered all adjacent nodes - mutable std::pair discoveryFinish; - - // Define a comparator for std::sort - // + This will help to sort nodes by finished time after traversal - static bool FinishedSort(const Node &node1, const Node &node2) - { return node1.discoveryFinish.second < node2.discoveryFinish.second;} - // Define operator== for std::find; And comparisons between nodes bool operator==(const Node &b) const { return this->number == b.number;} // Define an operator!= for comparing nodes for inequality @@ -83,24 +92,21 @@ public: // Constructor explicit Graph(std::vector nodes) : nodes_(std::move(nodes)) {} - // Breadth First Search - void BFS(const Node& startNode) const; + InfoBFS BFS(const Node& startNode) const; std::deque PathBFS(const Node &start, const Node &finish) const; - // Depth First Search - void DFS() const; + InfoDFS DFS() const; // An alternate DFS that checks each node of the graph beginning at startNode - void DFS(const Node &startNode) const; + InfoDFS DFS(const Node &startNode) const; // Visit function is used in both versions of DFS - void DFSVisit(int &time, const Node& startNode) const; + void DFSVisit(int &time, const Node& startNode, InfoDFS &searchInfo) const; // Topological sort, using DFS std::vector TopologicalSort(const Node &startNode) const; - // Returns a copy of a node with the number i within the graph - // + This uses the private, non-const accessor GetNode() + // + This uses the private, non-const accessor GetNode() and returns a copy inline Node GetNodeCopy(int i) { return GetNode(i);} // Return a constant iterator for reading node values inline std::vector::const_iterator NodeBegin() { return nodes_.cbegin();} @@ -109,7 +115,7 @@ private: // A non-const accessor for direct access to a node with the number value i inline Node & GetNode(int i) { return *std::find(nodes_.begin(), nodes_.end(), Node(i, {}));} - // For use with const member functions to access mutable values + // For grabbing a const qualified node inline const Node & GetNode(int i) const { return *std::find(nodes_.begin(), nodes_.end(), Node(i, {}));}