From 348586ec383c37b10a5b64beb8f7a2be8a090836 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Mon, 28 Jun 2021 12:46:04 -0400 Subject: [PATCH] Update object graph implementation to track node discover and finish time + Allows traversal and topological sort algorithms to show examples from MIT Algorithms more accurately --- cpp/algorithms/graphs/CMakeLists.txt | 1 + cpp/algorithms/graphs/object/graph.cpp | 28 +++++------ cpp/algorithms/graphs/object/lib-graph.cpp | 55 ++++++---------------- cpp/algorithms/graphs/object/lib-graph.hpp | 49 ++++++++++++------- 4 files changed, 58 insertions(+), 75 deletions(-) diff --git a/cpp/algorithms/graphs/CMakeLists.txt b/cpp/algorithms/graphs/CMakeLists.txt index 8ac7ee9..77a081a 100644 --- a/cpp/algorithms/graphs/CMakeLists.txt +++ b/cpp/algorithms/graphs/CMakeLists.txt @@ -16,3 +16,4 @@ project ( ) add_subdirectory(simple) +add_subdirectory(object) diff --git a/cpp/algorithms/graphs/object/graph.cpp b/cpp/algorithms/graphs/object/graph.cpp index 4e03a6e..cfd71ef 100644 --- a/cpp/algorithms/graphs/object/graph.cpp +++ b/cpp/algorithms/graphs/object/graph.cpp @@ -14,7 +14,7 @@ int main (const int argc, const char * argv[]) { // We could initialize the graph with some localNodes... - std::map> localNodes{ + std::map> localNodes{ {1, {2, 5}}, // Node 1 {2, {1, 6}}, // Node 2 {3, {4, 6, 7}}, @@ -26,13 +26,6 @@ int main (const int argc, const char * argv[]) }; // Graph bfsGraph(localNodes); -// Graph testGraph( -// { -// {Node(1, {2, 5})}, -//// {Node(1, {2, 5})}, -// } -// ) - std::cout << "\n\n##### Breadth First Search #####\n"; // Or we could use an initializer list... @@ -77,19 +70,20 @@ int main (const int argc, const char * argv[]) // Initialize an example graph for Depth First Search Graph topologicalGraph ( { - {1, {4, 5}}, - {2, {5}}, - {3, {}}, - {4, {5, 7}}, - {5, {}}, - {6, {7, 8}}, - {7, {9}}, - {8, {9}}, - {9, {}}, + {6, {8, 7}}, // shirt + {8, {9}}, // tie + {7, {9}}, // belt + {9, {}}, // jacket + {3, {}}, // watch + {1, {4, 5}}, // undershorts + {4, {5, 7}}, // pants + {5, {}}, // shoes + {2, {5}}, // socks } ); // The graph traversed in this example is seen in MIT Intro to Algorithms // + Chapter 22, Figure 22.4 on DFS + // Unlike the simple-graph example, this final result matches MIT Algorithms std::vector order = topologicalGraph.TopologicalSort(); std::cout << "\n\nTopological order: "; while (!order.empty()) { diff --git a/cpp/algorithms/graphs/object/lib-graph.cpp b/cpp/algorithms/graphs/object/lib-graph.cpp index 50866ef..bf7dd3c 100644 --- a/cpp/algorithms/graphs/object/lib-graph.cpp +++ b/cpp/algorithms/graphs/object/lib-graph.cpp @@ -52,6 +52,7 @@ void Graph::DFS() const { // Track the nodes we have discovered for (const auto &node : nodes_) node.color = White; + int time = 0; // Visit each node in the graph for (const auto& node : nodes_) { @@ -60,69 +61,43 @@ void Graph::DFS() const if (node.color == White) { std::cout << "Found undiscovered node: " << node.number << std::endl; // Visiting the undiscovered node will check it's adjacent nodes - DFSVisit(node); + DFSVisit(time, node); } } } -void Graph::DFSVisit(const Node& startNode) const +void Graph::DFSVisit(int &time, const Node& startNode) const { startNode.color = Gray; + time++; + startNode.discoveryFinish.first = time; // Check the adjacent nodes of the startNode for (const auto &adjacent : startNode.adjacent) { + auto iter = std::find(nodes_.begin(), nodes_.end(), + Node(adjacent, {})); // If the adjacentNode is undiscovered, visit it // + Offset by 1 to account for 0 index of discovered vector - if (nodes_[adjacent - 1].color == White) { + if (iter->color == White) { std::cout << "Found undiscovered adjacentNode: " << nodes_[adjacent - 1].number << std::endl; // Visiting the undiscovered node will check it's adjacent nodes - DFSVisit(nodes_[adjacent - 1]); + DFSVisit(time, *iter); } } startNode.color = Black; + time++; + startNode.discoveryFinish.second = time; } std::vector Graph::TopologicalSort() const { - std::vector topologicalOrder; + DFS(); + std::vector topological(nodes_); - // Track the nodes we have discovered - for (const auto &node : nodes_) node.color = White; - - // 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) { - std::cout << "Found undiscovered node: " << node.number << std::endl; - // Visiting the undiscovered node will check it's adjacent nodes - TopologicalVisit(node, topologicalOrder); - } - } + std::sort(topological.begin(), topological.end(), Node::FinishedSort); // The topologicalOrder is read right-to-left in the final result // + Output is handled in main as FILO, similar to a stack - return topologicalOrder; -} - -void Graph::TopologicalVisit(const Node &startNode, - std::vector &order) const -{ - // Mark the node as visited so we don't visit it twice - startNode.color = Gray; - - // Check the adjacent nodes of the startNode - for (const auto& adjacent : startNode.adjacent) { - // If the adjacentNode is undiscovered, visit it - if (nodes_[adjacent - 1].color == White) { - std::cout << "Found undiscovered adjacentNode: " << adjacent << std::endl; - // Visiting the undiscovered node will check it's adjacent nodes - TopologicalVisit(nodes_[adjacent - 1], order); - } - } - startNode.color = Black; - - // Add startNode to the topologicalOrder - order.push_back(startNode); + return topological; } diff --git a/cpp/algorithms/graphs/object/lib-graph.hpp b/cpp/algorithms/graphs/object/lib-graph.hpp index 965ca0a..4d7495d 100644 --- a/cpp/algorithms/graphs/object/lib-graph.hpp +++ b/cpp/algorithms/graphs/object/lib-graph.hpp @@ -13,33 +13,48 @@ #include #include #include -#include #include #include #include - -// A vertex can also be referred to as a node -// + ... Unless you are a mathematician ^.^ -struct Vertex { - // This vertex's number - int number; - // A set of all vertices adjacent to this vertex - std::set adjacent; -}; +#include enum Color {White, Gray, Black}; struct Node { public: - Node(int num, std::set adj) : number(num), adjacent(std::move(adj)) {} + Node(const Node &rhs) = default; + Node & operator=(Node rhs) { + if (this == &rhs) return *this; + swap(*this, rhs); + return *this; + } + 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); + } + + Node(int num, std::vector adj) : + number(num), adjacent(std::move(adj)) {} int number; - std::set adjacent; + std::vector adjacent; // Mutable so we can update the color of the nodes during traversal mutable Color color = White; - std::vector predecessors; + // 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 + mutable std::pair discoveryFinish; -// bool operator<(const Node &node1) const { return number < node1.number;} - inline void setColor(Color newColor) const { color = newColor;} + // 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 + bool operator==(const Node &b) const { return this->number == b.number;} }; class Graph { @@ -49,10 +64,8 @@ public: void BFS(const Node& startNode) const; void DFS() const; - void DFSVisit(const Node& startNode) const; + void DFSVisit(int &time, const Node& startNode) const; std::vector TopologicalSort() const; - void TopologicalVisit(const Node &startNode, std::vector &order) const; - }; #endif // LIB_GRAPH_HPP