diff --git a/cpp/algorithms/graphs/object/graph.cpp b/cpp/algorithms/graphs/object/graph.cpp index cfd71ef..262f52b 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::vector localNodes{ {1, {2, 5}}, // Node 1 {2, {1, 6}}, // Node 2 {3, {4, 6, 7}}, @@ -24,34 +24,50 @@ int main (const int argc, const char * argv[]) {7, {3, 4, 6, 8}}, {8, {4, 6}}, }; -// Graph bfsGraph(localNodes); + Graph bfsGraphInit(localNodes); std::cout << "\n\n##### Breadth First Search #####\n"; // Or we could use an initializer list... // Initialize a example graph for Breadth First Search - Graph bfsGraph ( + Graph bfsGraph( { - {Node(1, {2, 5})}, // Node 1 - {Node(2, {1, 6})}, // Node 2... - {Node(3, {4, 6, 7})}, - {Node(4, {3, 7, 8})}, - {Node(5, {1})}, - {Node(6, {2, 3, 7})}, - {Node(7, {3, 4, 6, 8})}, - {Node(8, {4, 6})}, + {1, {2, 5}}, // Node 1 + {2, {1, 6}}, // Node 2... + {3, {4, 6, 7}}, + {4, {3, 7, 8}}, + {5, {1}}, + {6, {2, 3, 7}}, + {7, {3, 4, 6, 8}}, + {8, {4, 6}}, } ); // The graph traversed in this example is seen in MIT Intro to Algorithms // + Chapter 22, Figure 22.3 on BFS - auto iter = bfsGraph.nodes_.begin(); - std::advance(iter, 1); - bfsGraph.BFS(*iter); + 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 + auto path = bfsGraph.PathBFS( + bfsGraph.GetNodeCopy(1), bfsGraph.GetNodeCopy(7) + ); + // If we were returned an empty path, it doesn't exist + if (path.empty()) std::cout << "No valid path found!\n"; + else { + // If we were returned a path, print it + std::cout << "\nValid path from " << path.front()->number + << " to " << path.back()->number << ": "; + for (const auto &node : path) { + std::cout << node->number << " "; + } + std::cout << std::endl; + } std::cout << "\n\n##### Depth First Search #####\n"; // Initialize an example graph for Depth First Search - Graph dfsGraph ( + Graph dfsGraph( { {1, {2, 4}}, {2, {5}}, @@ -68,7 +84,41 @@ int main (const int argc, const char * argv[]) std::cout << "\n\n##### Topological Sort #####\n"; // Initialize an example graph for Depth First Search + // + The order of initialization is important + // + To produce the same result as seen in the book + // ++ If the order is changed, other valid topological orders will be found + // The book starts on the 'shirt' node (with the number 6, in this example) Graph topologicalGraph ( + { + {1, {4, 5}}, // undershorts + {2, {5}}, // socks + {3, {}}, // watch + {4, {5, 7}}, // pants + {5, {}}, // shoes + {6, {8, 7}}, // shirt + {7, {9}}, // belt + {8, {9}}, // tie + {9, {}}, // jacket + } + ); + + // 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 + // + Aside from the placement of the watch node, which is not connected + // + This is because the node is visited after all other nodes are finished + std::vector order = + topologicalGraph.TopologicalSort(topologicalGraph.GetNodeCopy(6)); + std::cout << "\n\nTopological order: "; + while (!order.empty()) { + std::cout << order.back().number << " "; + order.pop_back(); + } + std::cout << std::endl; + + // If we want the topological order to match what is seen in the book + // + We have to initialize the graph carefully to get this result - + Graph topologicalGraph2 ( { {6, {8, 7}}, // shirt {8, {9}}, // tie @@ -81,15 +131,11 @@ int main (const int argc, const char * argv[]) {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(); + auto order2 = topologicalGraph2.TopologicalSort(*topologicalGraph2.NodeBegin()); std::cout << "\n\nTopological order: "; - while (!order.empty()) { - std::cout << order.back().number << " "; - order.pop_back(); + while (!order2.empty()) { + std::cout << order2.back().number << " "; + order2.pop_back(); } std::cout << std::endl; - -} +} \ No newline at end of file diff --git a/cpp/algorithms/graphs/object/lib-graph.cpp b/cpp/algorithms/graphs/object/lib-graph.cpp index bf7dd3c..bd3f227 100644 --- a/cpp/algorithms/graphs/object/lib-graph.cpp +++ b/cpp/algorithms/graphs/object/lib-graph.cpp @@ -14,13 +14,19 @@ void Graph::BFS(const Node& startNode) const { // Track the nodes we have discovered // TODO: Do this at the end to maintain the state instead of at beginning? - for (const auto &node : nodes_) node.color = White; + for (const auto &node : nodes_) { + node.color = White; + node.distance = 0; + node.predecessor = nullptr; + } // 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; +// startNode.distance = 0; +// startNode.predecessor = nullptr; // Visit the startNode visitQueue.push(startNode); @@ -34,18 +40,47 @@ 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 (nodes_[adjacent - 1].color == White) { + if (GetNode(adjacent).color == White) { std::cout << "Found undiscovered adjacentNode: " << adjacent << "\n"; // Mark the adjacent node as in progress - nodes_[adjacent - 1].color = Gray; + GetNode(adjacent).color = Gray; + GetNode(adjacent).distance = thisNode.distance + 1; + GetNode(adjacent).predecessor = + const_cast(&GetNode(thisNode.number)); + // Add the discovered node the the visitQueue - visitQueue.push(nodes_[adjacent - 1]); + visitQueue.push(GetNode(adjacent)); } } // We are finished with this node and the adjacent nodes; Mark it discovered - thisNode.color = Black; + GetNode(thisNode.number).color = Black; } +} +std::deque Graph::PathBFS(const Node &start, const Node &finish) const +{ + std::deque path; + + BFS(start); + const Node * next = finish.predecessor; + bool isValid = false; + do { + // If we have reached the start node, we have found a valid path + if (*next == Node(start)) isValid = true; + + // Add the node to the path as we check each node + path.push_front(next); + + // Move to the next node + next = next->predecessor; + } while (next != nullptr); + path.push_back(new Node(finish)); + + // If we never found a valid path, erase all contents of the path + if (!isValid) path.erase(path.begin(), path.end()); + + // Return the path, the caller should handle empty paths accordingly + return path; } void Graph::DFS() const @@ -67,6 +102,42 @@ void Graph::DFS() const } +void Graph::DFS(const Node &startNode) const +{ + // Track the nodes we have discovered + for (const auto &node : nodes_) node.color = White; + int time = 0; + + Node begin = startNode; + auto startIter = std::find(nodes_.begin(), nodes_.end(), + Node(startNode.number, {}) + ); + + // Visit each node in the graph + while (startIter != nodes_.end()) { + std::cout << "Visiting node " << startIter->number << std::endl; + // If the startIter is undiscovered, visit it + if (startIter->color == White) { + std::cout << "Found undiscovered node: " << startIter->number << std::endl; + // Visiting the undiscovered node will check it's adjacent nodes + DFSVisit(time, *startIter); + } + startIter++; + } + startIter = nodes_.begin(); + + while (! (*startIter == startNode)) { + std::cout << "Visiting node " << startIter->number << std::endl; + // If the startIter is undiscovered, visit it + if (startIter->color == White) { + std::cout << "Found undiscovered node: " << startIter->number << std::endl; + // Visiting the undiscovered node will check it's adjacent nodes + DFSVisit(time, *startIter); + } + startIter++; + } +} + void Graph::DFSVisit(int &time, const Node& startNode) const { startNode.color = Gray; @@ -80,7 +151,7 @@ void Graph::DFSVisit(int &time, const Node& startNode) const // + Offset by 1 to account for 0 index of discovered vector if (iter->color == White) { std::cout << "Found undiscovered adjacentNode: " - << nodes_[adjacent - 1].number << std::endl; + << GetNode(adjacent).number << std::endl; // Visiting the undiscovered node will check it's adjacent nodes DFSVisit(time, *iter); } @@ -90,9 +161,9 @@ void Graph::DFSVisit(int &time, const Node& startNode) const startNode.discoveryFinish.second = time; } -std::vector Graph::TopologicalSort() const +std::vector Graph::TopologicalSort(const Node &startNode) const { - DFS(); + DFS(GetNode(startNode.number)); std::vector topological(nodes_); std::sort(topological.begin(), topological.end(), Node::FinishedSort); diff --git a/cpp/algorithms/graphs/object/lib-graph.hpp b/cpp/algorithms/graphs/object/lib-graph.hpp index 4d7495d..85164f9 100644 --- a/cpp/algorithms/graphs/object/lib-graph.hpp +++ b/cpp/algorithms/graphs/object/lib-graph.hpp @@ -18,16 +18,23 @@ #include #include +// 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}; +/******************************************************************************/ +// Node structure for representing a graph struct Node { public: + // Constructors Node(const Node &rhs) = default; Node & operator=(Node rhs) { if (this == &rhs) return *this; swap(*this, rhs); return *this; } + Node(int num, std::vector adj) : number(num), adjacent(std::move(adj)) {} + friend void swap(Node &a, Node &b) { std::swap(a.number, b.number); std::swap(a.adjacent, b.adjacent); @@ -35,13 +42,23 @@ public: std::swap(a.discoveryFinish, b.discoveryFinish); } - Node(int num, std::vector adj) : - number(num), adjacent(std::move(adj)) {} + // Don't allow anyone to change these values when using a const reference int number; std::vector adjacent; - // Mutable so we can update the color of the nodes during traversal + + // 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; - // Create a pair to track discovery / finish time + + // 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 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 @@ -51,21 +68,46 @@ public: // + 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;} }; + +/******************************************************************************/ +// Graph class declaration class Graph { public: + // Constructor explicit Graph(std::vector nodes) : nodes_(std::move(nodes)) {} - std::vector nodes_; + + // Breadth First Search void BFS(const Node& startNode) const; + std::deque PathBFS(const Node &start, const Node &finish) const; + + + // Depth First Search void DFS() const; + void DFS(const Node &startNode) const; void DFSVisit(int &time, const Node& startNode) const; - std::vector TopologicalSort() 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 + inline Node GetNodeCopy(int i) { return GetNode(i);} + // Return a constant iterator for reading node values + inline std::vector::const_iterator NodeBegin() { return nodes_.begin();} + +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 + inline const Node & GetNode(int i) const + { return *std::find(nodes_.begin(), nodes_.end(), Node(i, {}));} + + std::vector nodes_; }; #endif // LIB_GRAPH_HPP