Update simple-graph implementation to track discovery and finish time
+ Allows result of topological sort to match examples shown in MIT Algorithms + Correct order of initialization for all graphs and adjacent nodes in graph.cpp + Provide overloaded DFS for beginning at a specific node within the graph
This commit is contained in:
		
							parent
							
								
									3d0dfa63d1
								
							
						
					
					
						commit
						166d998508
					
				| @ -1,10 +1,10 @@ | ||||
| /*#############################################################################
 | ||||
| ## Author: Shaun Reed                                                        ## | ||||
| ## Legal: All Content (c) 2021 Shaun Reed, all rights reserved               ## | ||||
| ## About: Driver program to test a simple graph implementation               ## | ||||
| ##                                                                           ## | ||||
| ## Contact: shaunrd0@gmail.com  | URL: www.shaunreed.com | GitHub: shaunrd0  ## | ||||
| ############################################################################### | ||||
| /*##############################################################################
 | ||||
| ## Author: Shaun Reed                                                         ## | ||||
| ## Legal: All Content (c) 2021 Shaun Reed, all rights reserved                ## | ||||
| ## About: Driver program to test a simple graph implementation                ## | ||||
| ##                                                                            ## | ||||
| ## Contact: shaunrd0@gmail.com  | URL: www.shaunreed.com | GitHub: shaunrd0   ## | ||||
| ################################################################################ | ||||
| */ | ||||
| 
 | ||||
| #include "lib-graph.hpp" | ||||
| @ -13,17 +13,17 @@ | ||||
| int main (const int argc, const char * argv[]) | ||||
| { | ||||
|   // We could initialize the graph with some localNodes...
 | ||||
|   std::map<int, std::set<int>> localNodes{ | ||||
|       {1, {2, 5}}, // Node 1
 | ||||
|       {2, {1, 6}}, // Node 2
 | ||||
|       {3, {4, 6, 7}}, | ||||
|       {4, {3, 7, 8}}, | ||||
|   std::unordered_map<int, std::unordered_set<int>> localNodes{ | ||||
|       {8, {6, 4}}, | ||||
|       {7, {8, 6, 4, 3}}, | ||||
|       {6, {7, 3, 2}}, | ||||
|       {5, {1}}, | ||||
|       {6, {2, 3, 7}}, | ||||
|       {7, {3, 4, 6, 8}}, | ||||
|       {8, {4, 6}}, | ||||
|       {4, {8, 7, 3}}, | ||||
|       {3, {7, 6, 4}}, | ||||
|       {2, {6, 1}}, // Node 2...
 | ||||
|       {1, {5, 2}}, // Node 1
 | ||||
|   }; | ||||
|   //  Graph bfsGraph(localNodes);
 | ||||
|   Graph exampleGraph(localNodes); | ||||
| 
 | ||||
| 
 | ||||
|   std::cout << "\n\n##### Breadth First Search #####\n"; | ||||
| @ -31,14 +31,14 @@ int main (const int argc, const char * argv[]) | ||||
|   // Initialize a example graph for Breadth First Search
 | ||||
|   Graph bfsGraph ( | ||||
|       { | ||||
|           {1, {2, 5}}, // Node 1
 | ||||
|           {2, {1, 6}}, // Node 2...
 | ||||
|           {3, {4, 6, 7}}, | ||||
|           {4, {3, 7, 8}}, | ||||
|           {8, {6, 4}}, | ||||
|           {7, {8, 6, 4, 3}}, | ||||
|           {6, {7, 3, 2}}, | ||||
|           {5, {1}}, | ||||
|           {6, {2, 3, 7}}, | ||||
|           {7, {3, 4, 6, 8}}, | ||||
|           {8, {4, 6}}, | ||||
|           {4, {8, 7, 3}}, | ||||
|           {3, {7, 6, 4}}, | ||||
|           {2, {6, 1}}, // Node 2...
 | ||||
|           {1, {5, 2}}, // Node 1
 | ||||
|       } | ||||
|   ); | ||||
|   // The graph traversed in this example is seen in MIT Intro to Algorithms
 | ||||
| @ -50,12 +50,12 @@ int main (const int argc, const char * argv[]) | ||||
|   // Initialize an example graph for Depth First Search
 | ||||
|   Graph dfsGraph ( | ||||
|       { | ||||
|           {1, {2, 4}}, | ||||
|           {2, {5}}, | ||||
|           {3, {5, 6}}, | ||||
|           {4, {2}}, | ||||
|           {5, {4}}, | ||||
|           {6, {6}}, | ||||
|           {5, {4}}, | ||||
|           {4, {2}}, | ||||
|           {3, {6, 5}}, | ||||
|           {2, {5}}, | ||||
|           {1, {4, 2}}, | ||||
|       } | ||||
|   ); | ||||
|   // The graph traversed in this example is seen in MIT Intro to Algorithms
 | ||||
| @ -67,32 +67,62 @@ int main (const int argc, const char * argv[]) | ||||
|   // Initialize an example graph for Topological Sort
 | ||||
|   Graph topologicalGraph ( | ||||
|       { | ||||
|           {1, {4, 5}}, | ||||
|           {2, {5}}, | ||||
|           {3, {}}, | ||||
|           {4, {5, 7}}, | ||||
|           {5, {}}, | ||||
|           {6, {7, 8}}, | ||||
|           {7, {9}}, | ||||
|           {8, {9}}, | ||||
|           {9, {}}, | ||||
|           {8, {9}}, | ||||
|           {7, {9}}, | ||||
|           {6, {7, 8}}, | ||||
|           {5, {}}, | ||||
|           {4, {7, 5}}, | ||||
|           {3, {}}, | ||||
|           {2, {5}}, | ||||
|           {1, {5, 4}}, | ||||
|       } | ||||
|   ); | ||||
|   auto order = topologicalGraph.TopologicalSort(topologicalGraph.GetNode(6)); | ||||
|   std::cout << "\nTopological order: "; | ||||
|   while (!order.empty()) { | ||||
|     std::cout << order.back() << " "; | ||||
|     order.pop_back(); | ||||
|   } | ||||
|   std::cout << std::endl << 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 -
 | ||||
|   // Because this is an unordered_(map/set) 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
 | ||||
|   Graph topologicalGraph2 ( | ||||
|       { | ||||
|           {2, {5}},    // socks
 | ||||
|           {5, {}},     // shoes
 | ||||
|           {4, {7, 5}}, // pants
 | ||||
|           {1, {5, 4}}, // undershorts
 | ||||
|           {3, {}},     // watch
 | ||||
|           {9, {}},     // jacket
 | ||||
|           {7, {9}},    // belt
 | ||||
|           {8, {9}},    // tie
 | ||||
|           {6, {7, 8}}, // shirt
 | ||||
|       } | ||||
|   ); | ||||
| 
 | ||||
|   // The graph traversed in this example is seen in MIT Intro to Algorithms
 | ||||
|   // + Chapter 22, Figure 22.7 on Topological Sort
 | ||||
|   // + Each node was replaced with a value from left-to-right, top-to-bottom
 | ||||
|   // + Undershorts = 1, Socks = 2, Watch = 3, Pants = 4, etc...
 | ||||
|   std::vector<int> order = topologicalGraph.TopologicalSort(); | ||||
|   std::vector<int> order2 = | ||||
|       topologicalGraph2.TopologicalSort(topologicalGraph2.NodeBegin()); | ||||
|   // Because this is a simple graph with no objects to store finishing time
 | ||||
|   // + The result is only one example of valid topological order
 | ||||
|   // + There are other valid orders; Final result differs from one in the book
 | ||||
|   std::cout << "\n\nTopological order: "; | ||||
|   while (!order.empty()) { | ||||
|     std::cout << order.back() << " "; | ||||
|     order.pop_back(); | ||||
|   std::cout << "\nTopological order: "; | ||||
|   while (!order2.empty()) { | ||||
|     std::cout << order2.back() << " "; | ||||
|     order2.pop_back(); | ||||
|   } | ||||
|   std::cout << std::endl; | ||||
| 
 | ||||
|   std::cout << std::endl; | ||||
| 
 | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| @ -8,6 +8,7 @@ | ||||
| ################################################################################ | ||||
| */ | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include "lib-graph.hpp" | ||||
| 
 | ||||
| 
 | ||||
| @ -55,6 +56,7 @@ void Graph::DFS() | ||||
| { | ||||
|   // Track the nodes we have discovered
 | ||||
|   std::vector<bool> discovered(nodes_.size(), false); | ||||
|   int time = 0; | ||||
| 
 | ||||
|   // Visit each node in the graph
 | ||||
|   for (const auto &node : nodes_) { | ||||
| @ -65,14 +67,55 @@ void Graph::DFS() | ||||
|       // Mark the node as visited so we don't visit it twice
 | ||||
|       discovered[node.first - 1] = true; | ||||
|       // Visiting the undiscovered node will check it's adjacent nodes
 | ||||
|       DFSVisit(node.first, discovered); | ||||
|       DFSVisit(time, node.first, discovered); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void Graph::DFSVisit(int startNode, std::vector<bool> &discovered) | ||||
| void Graph::DFS(Node::iterator startIter) | ||||
| { | ||||
|   // Track the nodes we have discovered
 | ||||
|   std::vector<bool> discovered(nodes_.size(), false); | ||||
|   int time = 0; | ||||
| 
 | ||||
|   auto startNode = GetNode(startIter->first); | ||||
| 
 | ||||
|   // beginning at startNode, visit each node in the graph until we reach the end
 | ||||
|   while (startIter != nodes_.end()) { | ||||
|     std::cout << "Visiting node " << startIter->first << std::endl; | ||||
|     // If the startIter is undiscovered, visit it
 | ||||
|     if (!discovered[startIter->first - 1]) { | ||||
|       std::cout << "Found undiscovered node: " << startIter->first << std::endl; | ||||
|       // Visiting the undiscovered node will check it's adjacent nodes
 | ||||
|       discovered[startIter->first - 1] = true; | ||||
|       DFSVisit(time, startIter->first, discovered); | ||||
|     } | ||||
|     startIter++; | ||||
|   } | ||||
| 
 | ||||
|   // Once we reach the last node, check the beginning for unchecked nodes
 | ||||
|   startIter = nodes_.begin(); | ||||
| 
 | ||||
|   // Once we reach the initial startNode, we have checked all nodes
 | ||||
|   while (startIter->first != startNode->first) { | ||||
|     std::cout << "Visiting node " << startIter->first << std::endl; | ||||
|     // If the startIter is undiscovered, visit it
 | ||||
|     if (!discovered[startIter->first - 1]) { | ||||
|       std::cout << "Found undiscovered node: " << startIter->first << std::endl; | ||||
|       // Visiting the undiscovered node will check it's adjacent nodes
 | ||||
|       discovered[startIter->first - 1] = true; | ||||
|       DFSVisit(time, startIter->first, discovered); | ||||
|     } | ||||
|     startIter++; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void Graph::DFSVisit(int &time, int startNode, std::vector<bool> &discovered) | ||||
| { | ||||
|   time++; | ||||
|   discoveryTime[startNode - 1] = std::make_pair(startNode, time); | ||||
| 
 | ||||
|   // Check the adjacent nodes of the startNode
 | ||||
|   // + Do not offset startNode by 1, since we use it as a key to a map
 | ||||
|   for (auto &adjacent : nodes_[startNode]) { | ||||
| @ -83,31 +126,25 @@ void Graph::DFSVisit(int startNode, std::vector<bool> &discovered) | ||||
|       discovered[adjacent - 1] = true; | ||||
| 
 | ||||
|       // Visiting the undiscovered node will check it's adjacent nodes
 | ||||
|       DFSVisit(adjacent, discovered); | ||||
|       DFSVisit(time, adjacent, discovered); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   time++; | ||||
|   finishTime[startNode - 1] = std::make_pair(startNode, time); | ||||
| } | ||||
| 
 | ||||
| std::vector<int> Graph::TopologicalSort() | ||||
| std::vector<int> Graph::TopologicalSort(Node::iterator startNode) | ||||
| { | ||||
|   DFS(startNode); | ||||
| 
 | ||||
|   std::vector<int> topologicalOrder; | ||||
| 
 | ||||
|   // Track the nodes we have discovered
 | ||||
|   std::vector<bool> discovered(nodes_.size(), false); | ||||
|   std::vector<std::pair<int, int>> finishOrder(finishTime); | ||||
| 
 | ||||
|   // Visit each node in the graph
 | ||||
|   for (const auto &node : nodes_) { | ||||
|     std::cout << "Visiting node " << node.first << std::endl; | ||||
|     // If the node is undiscovered, visit it
 | ||||
|     // + Offset by 1 to account for 0 index of discovered vector
 | ||||
|     if (!discovered[node.first - 1]) { | ||||
|       std::cout << "Found undiscovered node: " << node.first << std::endl; | ||||
|   std::sort(finishOrder.begin(), finishOrder.end(), Graph::FinishedSort); | ||||
| 
 | ||||
|       // Visiting the undiscovered node will check it's adjacent nodes
 | ||||
|       TopologicalVisit(node.first, discovered, topologicalOrder); | ||||
|     } | ||||
|   } | ||||
|   for (const auto &node : finishOrder) topologicalOrder.push_back(node.first); | ||||
| 
 | ||||
|   // The topologicalOrder is read right-to-left in the final result
 | ||||
|   // + Output is handled in main as FILO, similar to a stack
 | ||||
|  | ||||
| @ -15,22 +15,48 @@ | ||||
| #include <queue> | ||||
| #include <set> | ||||
| #include <vector> | ||||
| #include <unordered_map> | ||||
| #include <unordered_set> | ||||
| 
 | ||||
| 
 | ||||
| class Graph { | ||||
| public: | ||||
|   explicit Graph(std::map<int, std::set<int>> nodes) : nodes_(std::move(nodes)) {} | ||||
|   std::map<int, std::set<int>> nodes_; | ||||
|   using Node = std::unordered_map<int, std::unordered_set<int>>; | ||||
|   explicit Graph(Node nodes) : nodes_(std::move(nodes)) | ||||
|   { | ||||
|     discoveryTime.resize(nodes_.size()); | ||||
|     finishTime.resize(nodes_.size(), std::make_pair(0,0)); | ||||
|   } | ||||
| 
 | ||||
|   void BFS(int startNode); | ||||
| 
 | ||||
|   void DFS(); | ||||
|   void DFSVisit(int startNode, std::vector<bool> &discovered); | ||||
|   void DFS(Node::iterator startNode); | ||||
|   void DFSVisit(int &time, int startNode, std::vector<bool> &discovered); | ||||
| 
 | ||||
|   std::vector<int> TopologicalSort(); | ||||
|   std::vector<int> TopologicalSort(Node::iterator startNode); | ||||
|   void TopologicalVisit( | ||||
|       int startNode, std::vector<bool> &discovered, std::vector<int> &order | ||||
|   ); | ||||
| 
 | ||||
|   // Define a comparator for std::sort
 | ||||
|   // + This will help to sort nodes by finished time after traversal
 | ||||
|   static bool FinishedSort(std::pair<int, int> &node1, decltype(node1) &node2) | ||||
|   { return node1.second < node2.second;} | ||||
| 
 | ||||
|   inline Node::iterator NodeBegin() { return nodes_.begin();} | ||||
|   // A non-const accessor for direct access to a node with the number value i
 | ||||
|   inline Node::iterator GetNode(int i) { return nodes_.find(i);} | ||||
| 
 | ||||
| private: | ||||
|   // Unordered to avoid container reorganizing elements
 | ||||
|   // + Since this would alter the order nodes are traversed in
 | ||||
|   Node nodes_; | ||||
| 
 | ||||
|   // Where the first element in the following two pairs is the node number
 | ||||
|   // And the second element is the discovery / finish time
 | ||||
|   std::vector<std::pair<int, int>> discoveryTime; | ||||
|   std::vector<std::pair<int, int>> finishTime; | ||||
| }; | ||||
| 
 | ||||
| #endif // LIB_GRAPH_HPP
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user