From 1fc34d2dd4d630e59416f3d52bac6fe01054f782 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 19 Jun 2021 16:08:15 -0400 Subject: [PATCH] Add example of simple graph algorithms + Using pseudocode examples from MIT Intro to Algorithms --- cpp/algorithms/CMakeLists.txt | 1 + cpp/algorithms/graphs/CMakeLists.txt | 18 +++ cpp/algorithms/graphs/simple/CMakeLists.txt | 22 ++++ cpp/algorithms/graphs/simple/graph.cpp | 96 ++++++++++++++ cpp/algorithms/graphs/simple/lib-graph.cpp | 137 ++++++++++++++++++++ cpp/algorithms/graphs/simple/lib-graph.hpp | 36 +++++ 6 files changed, 310 insertions(+) create mode 100644 cpp/algorithms/graphs/CMakeLists.txt create mode 100644 cpp/algorithms/graphs/simple/CMakeLists.txt create mode 100644 cpp/algorithms/graphs/simple/graph.cpp create mode 100644 cpp/algorithms/graphs/simple/lib-graph.cpp create mode 100644 cpp/algorithms/graphs/simple/lib-graph.hpp diff --git a/cpp/algorithms/CMakeLists.txt b/cpp/algorithms/CMakeLists.txt index 5b488ef..a657747 100644 --- a/cpp/algorithms/CMakeLists.txt +++ b/cpp/algorithms/CMakeLists.txt @@ -15,5 +15,6 @@ project ( LANGUAGES CXX ) +add_subdirectory(graphs) add_subdirectory(sorting) add_subdirectory(trees) diff --git a/cpp/algorithms/graphs/CMakeLists.txt b/cpp/algorithms/graphs/CMakeLists.txt new file mode 100644 index 0000000..8ac7ee9 --- /dev/null +++ b/cpp/algorithms/graphs/CMakeLists.txt @@ -0,0 +1,18 @@ +############################################################################### +## Author: Shaun Reed ## +## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## +## About: A root project for practicing graph algorithms in C++ ## +## ## +## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## +############################################################################## +# +cmake_minimum_required(VERSION 3.15) + +project ( + #[[NAME]] Graphs + VERSION 1.0 + DESCRIPTION "A project for practicing algorithms using graphs in C++" + LANGUAGES CXX +) + +add_subdirectory(simple) diff --git a/cpp/algorithms/graphs/simple/CMakeLists.txt b/cpp/algorithms/graphs/simple/CMakeLists.txt new file mode 100644 index 0000000..34fc97a --- /dev/null +++ b/cpp/algorithms/graphs/simple/CMakeLists.txt @@ -0,0 +1,22 @@ +############################################################################### +## Author: Shaun Reed ## +## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## +## About: A CMakeLists configuration to test a simple graph implementation ## +## ## +## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## +############################################################################## +# + +cmake_minimum_required(VERSION 3.15) + +project( + #[[NAME]] SimpleGraph + VERSION 1.0 + DESCRIPTION "Practice implementing and using simple graphs in C++" + LANGUAGES CXX +) + +add_library(lib-graph-simple "lib-graph.cpp") + +add_executable(graph-test-simple "graph.cpp") +target_link_libraries(graph-test-simple lib-graph-simple) diff --git a/cpp/algorithms/graphs/simple/graph.cpp b/cpp/algorithms/graphs/simple/graph.cpp new file mode 100644 index 0000000..c86b9de --- /dev/null +++ b/cpp/algorithms/graphs/simple/graph.cpp @@ -0,0 +1,96 @@ +/*############################################################################# +## 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" + + +int main (const int argc, const char * argv[]) +{ + // We could initialize the graph with some localNodes... + std::map> localNodes{ + {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}}, + }; +// Graph bfsGraph(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 ( + { + {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 + bfsGraph.BFS(2); + + + std::cout << "\n\n##### Depth First Search #####\n"; + // Initialize an example graph for Depth First Search + Graph dfsGraph ( + { + {1, {2, 4}}, + {2, {5}}, + {3, {5, 6}}, + {4, {2}}, + {5, {4}}, + {6, {6}}, + } + ); + // The graph traversed in this example is seen in MIT Intro to Algorithms + // + Chapter 22, Figure 22.4 on DFS + dfsGraph.DFS(); + + + std::cout << "\n\n##### Topological Sort #####\n"; + // 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, {}}, + } + ); + // 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 order = topologicalGraph.TopologicalSort(); + // 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 << std::endl; + +} diff --git a/cpp/algorithms/graphs/simple/lib-graph.cpp b/cpp/algorithms/graphs/simple/lib-graph.cpp new file mode 100644 index 0000000..175b95c --- /dev/null +++ b/cpp/algorithms/graphs/simple/lib-graph.cpp @@ -0,0 +1,137 @@ +/*############################################################################## +## Author: Shaun Reed ## +## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## +## About: An example of a simple graph implementation ## +## Algorithms in this example are found in MIT Intro to Algorithms ## +## ## +## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## +################################################################################ +*/ + +#include "lib-graph.hpp" + + +void Graph::BFS(int startNode) +{ + // Track the nodes we have discovered + std::vector discovered(nodes_.size()); + for (bool node : discovered) node = false; // Initialize all nodes to false + + // Create a queue to visit discovered nodes in FIFO order + std::queue visitQueue; + + // Visit the startNode + discovered[startNode] = true; + visitQueue.push(startNode); + + // Continue to visit nodes until there are none left in the graph + while (!visitQueue.empty()) { + std::cout << "Visiting node " << visitQueue.front() << std::endl; + + // Remove thisNode from the visitQueue, storing its vertex locally + int thisNode = visitQueue.front(); + visitQueue.pop(); + + // Check if we have already discovered all the adjacentNodes to thisNode + for (const auto &adjacent : nodes_[thisNode]) { + if (!discovered[adjacent]) { + std::cout << "Found undiscovered adjacentNode: " << adjacent << "\n"; + // 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 + // In the visitQueue twice + discovered[adjacent] = true; + // Add the discovered node the the visitQueue + visitQueue.push(adjacent); + } + } + + } + +} + +void Graph::DFS() +{ + // Track the nodes we have discovered + std::vector discovered(nodes_.size()); + for (auto node : discovered) node = false; // Initialize nodes to false + + // 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 + if (!discovered[node.first]) { + std::cout << "Found undiscovered node: " << node.first << std::endl; + // Mark the node as visited so we don't visit it twice + discovered[node.first] = true; + // Visiting the undiscovered node will check it's adjacent nodes + DFSVisit(node.first, discovered); + } + } + +} + +void Graph::DFSVisit(int startNode, std::vector &discovered) +{ + // Check the adjacent nodes of the startNode + for (auto &adjacent : nodes_[startNode]) { + // If the adjacentNode is undiscovered, visit it + if (!discovered[adjacent]) { + std::cout << "Found undiscovered adjacentNode: " << adjacent << std::endl; + // Mark the node as visited so we don't visit it twice + discovered[adjacent] = true; + + // Visiting the undiscovered node will check it's adjacent nodes + DFSVisit(adjacent, discovered); + } + } + +} + +std::vector Graph::TopologicalSort() +{ + std::vector topologicalOrder; + + // Track the nodes we have discovered + std::vector discovered(nodes_.size()); + for (auto node : discovered) node = false; // Initialize nodes to false + + // 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; + + // Visiting the undiscovered node will check it's adjacent nodes + TopologicalVisit(node.first, discovered, topologicalOrder); + } + } + + // 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( + int startNode, std::vector &discovered, std::vector &order +) +{ + // Mark the node as visited so we don't visit it twice + discovered[startNode - 1] = true; + + // Check the adjacent nodes of the startNode + for (auto &adjacent : nodes_[startNode]) { + // If the adjacentNode is undiscovered, visit it + if (!discovered[adjacent - 1]) { + std::cout << "Found undiscovered adjacentNode: " << adjacent << std::endl; + + // Visiting the undiscovered node will check it's adjacent nodes + TopologicalVisit(adjacent, discovered, order); + } + } + + // Add startNode to the topologicalOrder + order.push_back(startNode); +} diff --git a/cpp/algorithms/graphs/simple/lib-graph.hpp b/cpp/algorithms/graphs/simple/lib-graph.hpp new file mode 100644 index 0000000..f4abc9e --- /dev/null +++ b/cpp/algorithms/graphs/simple/lib-graph.hpp @@ -0,0 +1,36 @@ +/*############################################################################# +## Author: Shaun Reed ## +## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## +## About: An example of a simple graph implementation ## +## Algorithms in this example are found in MIT Intro to Algorithms ## +## ## +## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## +############################################################################### +*/ +#ifndef LIB_GRAPH_HPP +#define LIB_GRAPH_HPP + +#include +#include +#include +#include +#include + + +class Graph { +public: + explicit Graph(std::map> nodes) : nodes_(std::move(nodes)) {} + std::map> nodes_; + + void BFS(int startNode); + + void DFS(); + void DFSVisit(int startNode, std::vector &discovered); + + std::vector TopologicalSort(); + void TopologicalVisit( + int startNode, std::vector &discovered, std::vector &order + ); +}; + +#endif // LIB_GRAPH_HPP