From a450465c2332ccdc2ba753f19acaaef0e69ec4c3 Mon Sep 17 00:00:00 2001 From: ZigRazor Date: Fri, 16 Oct 2020 11:02:09 +0000 Subject: [PATCH] Added DFS Algorithm with tests, improved performance for BFS when the node is not in the graph Signed-off-by: GitHub --- CMakeLists.txt | 4 +- README.md | 13 +++++ include/Graph.hpp | 93 +++++++++++++++++++++++++-------- test/DFSTest.cpp | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 216 insertions(+), 24 deletions(-) create mode 100644 test/DFSTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cb780865f..e7ccfadb5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.9) # set the project name and version -project(CXXGraph VERSION 0.0.3) +project(CXXGraph VERSION 0.0.4) configure_file(CXXGraphConfig.h.in ${PROJECT_SOURCE_DIR}/include/CXXGraphConfig.h) @@ -30,6 +30,7 @@ add_executable(test_exe test/main.cpp test/GraphTest.cpp test/DijkstraTest.cpp test/BFSTest.cpp + test/DFSTest.cpp ) target_include_directories(test_exe PUBLIC "${PROJECT_SOURCE_DIR}/include" @@ -51,4 +52,5 @@ add_test(test_undirectedweightededge test_exe --gtest_filter=UndirectedWeightedE add_test(test_graph test_exe --gtest_filter=GraphTest*) add_test(test_dijkstra test_exe --gtest_filter=DijkstraTest*) add_test(test_bfs test_exe --gtest_filter=BFSTest*) +add_test(test_dfs test_exe --gtest_filter=DFSTest*) diff --git a/README.md b/README.md index b8dcc7085..4b7e2ff03 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,19 @@ Applications of Breadth First Search are : And there are many more... + #### DFS (Depth First Search) + [Depth First Search Algorithm (Depth First Search)](https://en.wikipedia.org/wiki/Depth-first_search) + **Depth First Search**, also quoted as **DFS**, is a Graph Traversal Algorithm. Time Complexity O(|V| + |E|) where V is number of vertices and E is number of edges in graph. + Application of Depth First Search are: + 1. Finding connected components + 2. Finding 2-(edge or vertex)-connected components. + 3. Finding 3-(edge or vertex)-connected components. + 4. Finding the bridges of a graph. + 5. Generating words in order to plot the limit set of a group. + 6. Finding strongly connected components. + + And there are many more... + WORK IN PROGRESS ## Classes Explanation diff --git a/include/Graph.hpp b/include/Graph.hpp index 947a0c833..e16a5d709 100644 --- a/include/Graph.hpp +++ b/include/Graph.hpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace CXXGRAPH { @@ -497,6 +498,16 @@ namespace CXXGRAPH * */ const std::vector> breadth_first_search(const Node &start) const; + /** + * \brief + * Function performs the depth first search algorithm over the graph + * + * @param start Node from where traversing starts + * @returns a vector of Node indicating which Node were visited during the + * search. + * + */ + const std::vector> depth_first_search(const Node &start) const; friend std::ostream &operator<<<>(std::ostream &os, const Graph &graph); friend std::ostream &operator<<<>(std::ostream &os, const AdjacencyMatrix &adj); @@ -535,12 +546,13 @@ namespace CXXGRAPH edgeSet.erase(edgeSet.find(edgeOpt.value())); } } - - template + + template const std::set *> Graph::getNodeSet() const { std::set *> nodeSet; - for (auto edge : edgeSet){ + for (auto edge : edgeSet) + { nodeSet.insert(edge->getNodePair().first); nodeSet.insert(edge->getNodePair().second); } @@ -607,11 +619,13 @@ namespace CXXGRAPH result.errorMessage = ""; result.result = INF_DOUBLE; auto nodeSet = getNodeSet(); - if(nodeSet.find(&source) == nodeSet.end()){ // check if source node exist in the graph + if (nodeSet.find(&source) == nodeSet.end()) + { // check if source node exist in the graph result.errorMessage = ERR_DIJ_SOURCE_NODE_NOT_IN_GRAPH; return result; } - if(nodeSet.find(&target) == nodeSet.end()){ // check if target node exist in the graph + if (nodeSet.find(&target) == nodeSet.end()) + { // check if target node exist in the graph result.errorMessage = ERR_DIJ_TARGET_NODE_NOT_IN_GRAPH; return result; } @@ -707,36 +721,69 @@ namespace CXXGRAPH template const std::vector> Graph::breadth_first_search(const Node &start) const { - const AdjacencyMatrix adj = getAdjMatrix(); /// vector to keep track of visited nodes std::vector> visited; + auto nodeSet = getNodeSet(); + if (nodeSet.find(&start) == nodeSet.end()) //check is exist node in the graph + { + return visited; + } + const AdjacencyMatrix adj = getAdjMatrix(); /// queue that stores vertices that need to be further explored std::queue *> tracker; - auto nodeSet = getNodeSet(); - if (nodeSet.find(&start) != nodeSet.end()) //check is exist node in the graph + + /// mark the starting node as visited + visited.push_back(start); + tracker.push(&start); + while (!tracker.empty()) { - /// mark the starting node as visited - visited.push_back(start); - tracker.push(&start); - while (!tracker.empty()) + const Node *node = tracker.front(); + tracker.pop(); + if (adj.find(node) != adj.end()) { - const Node *node = tracker.front(); - tracker.pop(); - if (adj.find(node) != adj.end()) + for (auto elem : adj.at(node)) { - for (auto elem : adj.at(node)) + /// if the node is not visited then mark it as visited + /// and push it to the queue + if (std::find(visited.begin(), visited.end(), *(elem.first)) == visited.end()) { - /// if the node is not visited then mark it as visited - /// and push it to the queue - if (std::find(visited.begin(), visited.end(), *(elem.first)) == visited.end()) - { - visited.push_back(*(elem.first)); - tracker.push(elem.first); - } + visited.push_back(*(elem.first)); + tracker.push(elem.first); } } } } + + return visited; + } + + template + const std::vector> Graph::depth_first_search(const Node &start) const + { + /// vector to keep track of visited nodes + std::vector> visited; + auto nodeSet = getNodeSet(); + if (nodeSet.find(&start) == nodeSet.end()) //check is exist node in the graph + { + return visited; + } + const AdjacencyMatrix adj = getAdjMatrix(); + std::function &, const Node &, std::vector> &)> explore; + explore = [&explore](const AdjacencyMatrix &adj, const Node &node, std::vector> &visited) -> void { + visited.push_back(node); + if (adj.find(&node) != adj.end()) + { + for (auto x : adj.at(&node)) + { + if (std::find(visited.begin(), visited.end(), *(x.first)) == visited.end()) + { + explore(adj, *(x.first), visited); + } + } + } + }; + explore(adj, start, visited); + return visited; } diff --git a/test/DFSTest.cpp b/test/DFSTest.cpp new file mode 100644 index 000000000..b5d822ff1 --- /dev/null +++ b/test/DFSTest.cpp @@ -0,0 +1,130 @@ +#include "gtest/gtest.h" +#include "Graph.hpp" + +TEST(DFSTest, test_1) +{ + CXXGRAPH::Node node1(1, 1); + CXXGRAPH::Node node2(2, 2); + CXXGRAPH::Node node3(3, 3); + std::pair *, const CXXGRAPH::Node *> pairNode(&node1, &node2); + CXXGRAPH::DirectedWeightedEdge edge1(1, pairNode,1); + CXXGRAPH::DirectedWeightedEdge edge2(2, node2, node3,1); + CXXGRAPH::UndirectedWeightedEdge edge3(3, node1, node3,6); + std::set *> edgeSet; + edgeSet.insert(&edge1); + edgeSet.insert(&edge2); + edgeSet.insert(&edge3); + CXXGRAPH::Graph graph(edgeSet); + std::vector> res= graph.depth_first_search(node1); + ASSERT_EQ(res.size(),3); + ASSERT_TRUE(std::find(res.begin(), res.end(), node1) != res.end()); + ASSERT_TRUE(std::find(res.begin(), res.end(), node2) != res.end()); + ASSERT_TRUE(std::find(res.begin(), res.end(), node3) != res.end()); +} + +TEST(DFSTest, test_2) +{ + CXXGRAPH::Node node1(1, 1); + CXXGRAPH::Node node2(2, 2); + CXXGRAPH::Node node3(3, 3); + std::pair *, const CXXGRAPH::Node *> pairNode(&node1, &node2); + CXXGRAPH::DirectedWeightedEdge edge1(1, pairNode,1); + CXXGRAPH::DirectedWeightedEdge edge2(2, node2, node3,1); + CXXGRAPH::DirectedWeightedEdge edge3(3, node1, node3,6); + std::set *> edgeSet; + edgeSet.insert(&edge1); + edgeSet.insert(&edge2); + edgeSet.insert(&edge3); + CXXGRAPH::Graph graph(edgeSet); + std::vector> res= graph.depth_first_search(node2); + ASSERT_EQ(res.size(),2); + ASSERT_FALSE(std::find(res.begin(), res.end(), node1) != res.end()); + ASSERT_TRUE(std::find(res.begin(), res.end(), node2) != res.end()); + ASSERT_TRUE(std::find(res.begin(), res.end(), node3) != res.end()); +} + +TEST(DFSTest, test_3) +{ + CXXGRAPH::Node node1(1, 1); + CXXGRAPH::Node node2(2, 2); + CXXGRAPH::Node node3(3, 3); + std::pair *, const CXXGRAPH::Node *> pairNode(&node1, &node2); + CXXGRAPH::DirectedWeightedEdge edge1(1, pairNode,1); + CXXGRAPH::DirectedWeightedEdge edge2(2, node2, node3,1); + CXXGRAPH::UndirectedWeightedEdge edge3(3, node1, node3,6); + std::set *> edgeSet; + edgeSet.insert(&edge1); + edgeSet.insert(&edge2); + edgeSet.insert(&edge3); + CXXGRAPH::Graph graph(edgeSet); + std::vector> res= graph.depth_first_search(node2); + ASSERT_EQ(res.size(),3); + ASSERT_TRUE(std::find(res.begin(), res.end(), node1) != res.end()); + ASSERT_TRUE(std::find(res.begin(), res.end(), node2) != res.end()); + ASSERT_TRUE(std::find(res.begin(), res.end(), node3) != res.end()); +} + +TEST(DFSTest, test_4) +{ + CXXGRAPH::Node node1(1, 1); + CXXGRAPH::Node node2(2, 2); + CXXGRAPH::Node node3(3, 3); + std::pair *, const CXXGRAPH::Node *> pairNode(&node1, &node2); + CXXGRAPH::DirectedWeightedEdge edge1(1, pairNode,1); + CXXGRAPH::DirectedWeightedEdge edge2(2, node2, node3,1); + CXXGRAPH::UndirectedWeightedEdge edge3(3, node1, node3,6); + std::set *> edgeSet; + edgeSet.insert(&edge1); + edgeSet.insert(&edge2); + edgeSet.insert(&edge3); + CXXGRAPH::Graph graph(edgeSet); + std::vector> res= graph.depth_first_search(node3); + ASSERT_EQ(res.size(),3); + ASSERT_TRUE(std::find(res.begin(), res.end(), node1) != res.end()); + ASSERT_TRUE(std::find(res.begin(), res.end(), node2) != res.end()); + ASSERT_TRUE(std::find(res.begin(), res.end(), node3) != res.end()); +} + +TEST(DFSTest, test_5) +{ + CXXGRAPH::Node node1(1, 1); + CXXGRAPH::Node node2(2, 2); + CXXGRAPH::Node node3(3, 3); + std::pair *, const CXXGRAPH::Node *> pairNode(&node1, &node2); + CXXGRAPH::DirectedWeightedEdge edge1(1, pairNode,1); + CXXGRAPH::DirectedWeightedEdge edge2(2, node2, node3,1); + CXXGRAPH::DirectedWeightedEdge edge3(3, node1, node3,6); + std::set *> edgeSet; + edgeSet.insert(&edge1); + edgeSet.insert(&edge2); + edgeSet.insert(&edge3); + CXXGRAPH::Graph graph(edgeSet); + std::vector> res= graph.depth_first_search(node3); + ASSERT_EQ(res.size(),1); + ASSERT_FALSE(std::find(res.begin(), res.end(), node1) != res.end()); + ASSERT_FALSE(std::find(res.begin(), res.end(), node2) != res.end()); + ASSERT_TRUE(std::find(res.begin(), res.end(), node3) != res.end()); +} + +TEST(DFSTest, test_6) +{ + CXXGRAPH::Node node1(1, 1); + CXXGRAPH::Node node2(2, 2); + CXXGRAPH::Node node3(3, 3); + CXXGRAPH::Node node4(4, 4); + std::pair *, const CXXGRAPH::Node *> pairNode(&node1, &node2); + CXXGRAPH::DirectedWeightedEdge edge1(1, pairNode,1); + CXXGRAPH::DirectedWeightedEdge edge2(2, node2, node3,1); + CXXGRAPH::DirectedWeightedEdge edge3(3, node1, node3,6); + std::set *> edgeSet; + edgeSet.insert(&edge1); + edgeSet.insert(&edge2); + edgeSet.insert(&edge3); + CXXGRAPH::Graph graph(edgeSet); + std::vector> res= graph.depth_first_search(node4); + ASSERT_EQ(res.size(),0); + ASSERT_FALSE(std::find(res.begin(), res.end(), node1) != res.end()); + ASSERT_FALSE(std::find(res.begin(), res.end(), node2) != res.end()); + ASSERT_FALSE(std::find(res.begin(), res.end(), node3) != res.end()); + ASSERT_FALSE(std::find(res.begin(), res.end(), node4) != res.end()); +} \ No newline at end of file