diff --git a/CMakeLists.txt b/CMakeLists.txt index c9ee8af34..8623057c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,7 @@ add_test(test_rw_output test_exe --gtest_filter=RWOutputTest*) add_test(test_partition test_exe --gtest_filter=PartitionTest*) add_test(test_dial test_exe --gtest_filter=DialTest*) add_test(test_bestfirstsearch test_exe --gtest_filter=BestFirstSearch*) +add_test(test_kahn test_exe --gtest_filter=Kahn*) option(BENCHMARK "Enable Benchmark" OFF) if(BENCHMARK) diff --git a/README.md b/README.md index c2adcaf86..984e8d205 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ If you are interested, please contact us at zigrazor@gmail.com or contribute to - [Graph Slicing based on connectivity](#graph-slicing-based-on-connectivity) - [Ford-Fulkerson Algorithm](#ford-fulkerson-algorithm) - [Kosaraju's Algorithm](#kosarajus-algorithm) + - [Kahn's Algorithm](#kahn-algorithm) - [Partition Algorithm Explanation](#partition-algorithm-explanation) - [Vertex-Cut](#vertex-cut) - [Edge Balanced Vertex-Cut](#edge-balanced-vertex-cut) @@ -506,6 +507,10 @@ The idea behind the algorithm is as follows: as long as there is a path from the 2). Reverse directions of all arcs to obtain the transpose graph. 3). One by one pop a vertex from S while S is not empty. Let the popped vertex be ā€˜vā€™. Take v as source and do DFS (call DFSUtil(v)). The DFS starting from v prints strongly connected component of v. +### Kahn's Algorithm +[Kahn's Algorithm](https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm) finds topological +ordering by iteratively removing nodes in the graph which have no incoming edges. When a node is removed from the graph, it is added to the topological ordering and all its edges are removed allowing for the next set of nodes with no incoming edges to be selected. + ## Partition Algorithm Explanation ### Vertex-Cut diff --git a/include/Graph/Graph.hpp b/include/Graph/Graph.hpp index 0fc7262c0..b4aecbd6f 100644 --- a/include/Graph/Graph.hpp +++ b/include/Graph/Graph.hpp @@ -279,10 +279,10 @@ namespace CXXGRAPH * search. * */ - virtual const std::vector> breadth_first_search(const Node &start) const; + virtual const std::vector> breadth_first_search(const Node &start) const; /** * \brief - * The multithreaded version of breadth_first_search + * The multithreaded version of breadth_first_search * It turns out to be two indepentent functions because of implemntation differences * * @param start Node from where traversing starts @@ -381,10 +381,20 @@ namespace CXXGRAPH * @brief This function sort nodes in topological order. * Applicable for Directed Acyclic Graph * - * @return a vector containing nodes in topological order + * @return a struct with a vector of Nodes ordered topologically else ERROR in case + * of undirected or cyclic graph */ virtual TopoSortResult topologicalSort() const; + /** + * @brief This function sort nodes in topological order using kahn's algorithm + * Applicable for Directed Acyclic Graph + * + * @return a struct with a vector of Nodes ordered topologically else ERROR in case + * of undirected or cyclic graph + */ + virtual TopoSortResult kahn() const; + /** * \brief * This function performs performs the kosaraju algorthm on the graph to find the strongly connected components. @@ -1010,7 +1020,7 @@ namespace CXXGRAPH template const AdjacencyMatrix Graph::getAdjMatrix() const { - + AdjacencyMatrix adj; auto addElementToAdjMatrix = [&adj](const Node *nodeFrom, const Node *nodeTo, const Edge *edge){ std::pair *, const Edge *> elem = {nodeTo, edge}; @@ -1726,7 +1736,7 @@ namespace CXXGRAPH return bfs_result; } - std::unordered_map *, int> node_to_index; + std::unordered_map *, int> node_to_index; for (const auto &node : nodeSet) { node_to_index[node] = node_to_index.size(); @@ -1751,14 +1761,14 @@ namespace CXXGRAPH level_tracker.push_back(&start); // a worker is assigned a small part of tasks for each time - // assignments of tasks in current level and updates of tasks in next level are inclusive + // assignments of tasks in current level and updates of tasks in next level are inclusive std::mutex tracker_mutex; - std::mutex next_tracker_mutex; + std::mutex next_tracker_mutex; std::atomic assigned_tasks = 0; int num_tasks = 1; // unit of task assignment, which mean assign block_size tasks to a worker each time int block_size = 1; - int level = 1; + int level = 1; auto extract_tasks = [&level_tracker, &tracker_mutex, &assigned_tasks, &num_tasks, &block_size] () -> std::pair { @@ -1802,7 +1812,7 @@ namespace CXXGRAPH for (int i = start_index; i < end_index; ++i) { - if (adj.count(level_tracker[i])) + if (adj.count(level_tracker[i])) { for (const auto &elem : adj.at(level_tracker[i])) { @@ -1815,7 +1825,7 @@ namespace CXXGRAPH } } } - } + } // submit local result to global result if (!local_tracker.empty()) @@ -1823,7 +1833,7 @@ namespace CXXGRAPH submit_result(local_tracker); } - // last worker need to do preparation for the next iteration + // last worker need to do preparation for the next iteration int cur_level = level; if (num_threads == 1 + waiting_workers.fetch_add(1)) { @@ -1851,9 +1861,9 @@ namespace CXXGRAPH { // not to wait if last worker reachs last statement before notify all or even further std::unique_lock next_level_lock(next_level_mutex); - next_level_cond.wait(next_level_lock, + next_level_cond.wait(next_level_lock, [&level, cur_level] () { return level != cur_level;}); - } + } } }; @@ -2252,7 +2262,7 @@ namespace CXXGRAPH { TopoSortResult result; result.success = false; - + if (!isDirectedGraph()) { result.errorMessage = ERR_UNDIR_GRAPH; @@ -2263,7 +2273,7 @@ namespace CXXGRAPH result.errorMessage = ERR_CYCLIC_GRAPH; return result; } - else + else { const auto &adjMatrix = getAdjMatrix(); const auto &nodeSet = getNodeSet(); @@ -2278,7 +2288,7 @@ namespace CXXGRAPH for (const auto &edge : adjMatrix.at(curNode)) { const auto &nextNode = edge.first; - if (false == visited[nextNode]) + if (false == visited[nextNode]) { postorder_helper(nextNode); } @@ -2286,7 +2296,7 @@ namespace CXXGRAPH } result.nodesInTopoOrder.push_back(*curNode); - }; + }; int numNodes = adjMatrix.size(); result.nodesInTopoOrder.reserve(numNodes); @@ -2305,6 +2315,79 @@ namespace CXXGRAPH } } + template + TopoSortResult Graph::kahn() const + { + TopoSortResult result; + + if (!isDirectedGraph()) + { + result.errorMessage = ERR_UNDIR_GRAPH; + return result; + } + else + { + const auto adjMatrix = Graph::getAdjMatrix(); + const auto nodeSet = Graph::getNodeSet(); + result.nodesInTopoOrder.reserve(adjMatrix.size()); + + std::unordered_map indegree; + for (const auto &node : nodeSet) + { + indegree[node->getId()] = 0; + } + for (const auto &list : adjMatrix) + { + auto children = list.second; + for (const auto &child : children) + { + indegree[std::get<0>(child)->getId()]++; + } + } + + std::queue*> topologicalOrder; + + for (const auto &node : nodeSet) + { + if (!indegree[node->getId()]) + { + topologicalOrder.emplace(node); + } + } + + size_t visited = 0; + while(!topologicalOrder.empty()) + { + const Node *currentNode = topologicalOrder.front(); + topologicalOrder.pop(); + result.nodesInTopoOrder.push_back(*currentNode); + + if (adjMatrix.find(currentNode) != adjMatrix.end()) + { + for (const auto &child : adjMatrix.at(currentNode)) + { + if (--indegree[std::get<0>(child)->getId()] == 0) + { + topologicalOrder.emplace(std::get<0>(child)); + } + } + } + visited++; + + } + + if (visited != nodeSet.size()) + { + result.errorMessage = ERR_CYCLIC_GRAPH; + result.nodesInTopoOrder.clear(); + return result; + } + + result.success = true; + return result; + } + } + template std::vector>> Graph::kosaraju() const { diff --git a/test/KahnTest.cpp b/test/KahnTest.cpp new file mode 100644 index 000000000..8c77a681f --- /dev/null +++ b/test/KahnTest.cpp @@ -0,0 +1,167 @@ +#include "gtest/gtest.h" +#include "CXXGraph.hpp" +#include "Utility/Typedef.hpp" +#include "Utility/ConstString.hpp" + +TEST(KahnTest, error_cyclic_graph) +{ + 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, 2); + CXXGRAPH::DirectedWeightedEdge edge3(3, node3, node1, 3); + CXXGRAPH::T_EdgeSet edgeSet; + edgeSet.insert(&edge1); + edgeSet.insert(&edge2); + edgeSet.insert(&edge3); + CXXGRAPH::Graph graph(edgeSet); + + CXXGRAPH::TopoSortResult res = graph.kahn(); + ASSERT_EQ(res.success, false); + ASSERT_EQ(res.errorMessage, CXXGRAPH::ERR_CYCLIC_GRAPH); +} + +TEST(KahnTest, error_undirected_graph) +{ + CXXGRAPH::Node node1("1", 1); + CXXGRAPH::Node node2("2", 2); + CXXGRAPH::Node node3("3", 3); + CXXGRAPH::Node node4("4", 4); + CXXGRAPH::Node node5("5", 5); + std::pair *, const CXXGRAPH::Node *> pairNode(&node1, &node2); + CXXGRAPH::UndirectedWeightedEdge edge1(1, pairNode, 1); + CXXGRAPH::UndirectedWeightedEdge edge2(2, node2, node3, 1); + CXXGRAPH::UndirectedWeightedEdge edge3(3, node3, node4, 1); + CXXGRAPH::UndirectedWeightedEdge edge4(3, node3, node5, 1); + CXXGRAPH::T_EdgeSet edgeSet; + edgeSet.insert(&edge1); + edgeSet.insert(&edge2); + edgeSet.insert(&edge3); + edgeSet.insert(&edge4); + CXXGRAPH::Graph graph(edgeSet); + + CXXGRAPH::TopoSortResult && res = graph.kahn(); + ASSERT_EQ(res.success, false); + ASSERT_EQ(res.errorMessage, CXXGRAPH::ERR_UNDIR_GRAPH); +} + +TEST(KahnTest, correct_example_small) +{ + CXXGRAPH::Node node1("1", 1); + CXXGRAPH::Node node2("2", 2); + CXXGRAPH::Node node3("3", 3); + CXXGRAPH::Node node4("4", 4); + CXXGRAPH::Node node5("5", 5); + CXXGRAPH::Node node6("6", 6); + CXXGRAPH::Node node7("7", 7); + CXXGRAPH::Node node8("8", 8); + 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, node2, node6, 6); + CXXGRAPH::DirectedWeightedEdge edge4(4, node3, node4, 6); + CXXGRAPH::DirectedWeightedEdge edge5(5, node3, node5, 6); + CXXGRAPH::DirectedWeightedEdge edge6(6, node6, node7, 6); + CXXGRAPH::DirectedWeightedEdge edge7(7, node8, node6, 6); + CXXGRAPH::DirectedWeightedEdge edge8(8 , node8, node7, 6); + CXXGRAPH::T_EdgeSet edgeSet; + edgeSet.insert(&edge1); + edgeSet.insert(&edge2); + edgeSet.insert(&edge3); + edgeSet.insert(&edge4); + edgeSet.insert(&edge5); + edgeSet.insert(&edge6); + edgeSet.insert(&edge7); + edgeSet.insert(&edge8); + CXXGRAPH::Graph graph(edgeSet); + + CXXGRAPH::TopoSortResult res = graph.kahn(); + ASSERT_EQ(res.success, true); + ASSERT_TRUE(res.errorMessage.empty()); + ASSERT_EQ(res.nodesInTopoOrder.size(), graph.getNodeSet().size()); + + std::unordered_map topOrderNodeIds; + for (int i = 0; i < res.nodesInTopoOrder.size(); ++i) + { + topOrderNodeIds[res.nodesInTopoOrder[i].getId()] = i; + } + + for (const auto &node : graph.getNodeSet()) + { + ASSERT_TRUE(topOrderNodeIds.count(node->getId())); + } +} + +TEST(KahnTest, correct_example_big) +{ + CXXGRAPH::Node node0("0", 0); + CXXGRAPH::Node node1("1", 1); + CXXGRAPH::Node node2("2", 2); + CXXGRAPH::Node node3("3", 3); + CXXGRAPH::Node node4("4", 4); + CXXGRAPH::Node node5("5", 5); + CXXGRAPH::Node node6("6", 6); + CXXGRAPH::Node node7("7", 7); + CXXGRAPH::Node node8("8", 8); + CXXGRAPH::Node node9("9", 9); + CXXGRAPH::Node node10("10", 10); + CXXGRAPH::Node node11("11", 11); + CXXGRAPH::Node node12("12", 12); + CXXGRAPH::Node node13("13", 13); + CXXGRAPH::DirectedEdge edge1(1, node0, node2); + CXXGRAPH::DirectedEdge edge2(2, node0, node3); + CXXGRAPH::DirectedEdge edge3(3, node0, node6); + CXXGRAPH::DirectedEdge edge4(4, node1, node4); + CXXGRAPH::DirectedEdge edge5(5, node2, node6); + CXXGRAPH::DirectedEdge edge6(6, node3, node1); + CXXGRAPH::DirectedEdge edge7(7, node3, node4); + CXXGRAPH::DirectedEdge edge8(8 , node4, node5); + CXXGRAPH::DirectedEdge edge9(9 , node4, node8); + CXXGRAPH::DirectedEdge edge10(10 , node6, node7); + CXXGRAPH::DirectedEdge edge11(11 , node6, node11); + CXXGRAPH::DirectedEdge edge12(12 , node7, node4); + CXXGRAPH::DirectedEdge edge13(13 , node7, node12); + CXXGRAPH::DirectedEdge edge14(14 , node9, node2); + CXXGRAPH::DirectedEdge edge15(15 , node9, node10); + CXXGRAPH::DirectedEdge edge16(16 , node10, node6); + CXXGRAPH::DirectedEdge edge17(17 , node11, node12); + CXXGRAPH::DirectedEdge edge18(18 , node12, node8); + CXXGRAPH::T_EdgeSet edgeSet; + edgeSet.insert(&edge1); + edgeSet.insert(&edge2); + edgeSet.insert(&edge3); + edgeSet.insert(&edge4); + edgeSet.insert(&edge5); + edgeSet.insert(&edge6); + edgeSet.insert(&edge7); + edgeSet.insert(&edge8); + edgeSet.insert(&edge9); + edgeSet.insert(&edge10); + edgeSet.insert(&edge11); + edgeSet.insert(&edge12); + edgeSet.insert(&edge13); + edgeSet.insert(&edge14); + edgeSet.insert(&edge15); + edgeSet.insert(&edge16); + edgeSet.insert(&edge17); + edgeSet.insert(&edge18); + CXXGRAPH::Graph graph(edgeSet); + + CXXGRAPH::TopoSortResult res = graph.kahn(); + ASSERT_EQ(res.success, true); + ASSERT_TRUE(res.errorMessage.empty()); + ASSERT_EQ(res.nodesInTopoOrder.size(), graph.getNodeSet().size()); + + std::unordered_map topOrderNodeIds; + for (int i = 0; i < res.nodesInTopoOrder.size(); ++i) + { + topOrderNodeIds[res.nodesInTopoOrder[i].getId()] = i; + } + + for (const auto &node : graph.getNodeSet()) + { + ASSERT_TRUE(topOrderNodeIds.count(node->getId())); + } +}