Skip to content

Commit

Permalink
introduce topological sort for graph (#247)
Browse files Browse the repository at this point in the history
* implement topological sort based on dfs
* add benchmark for topological sort
* add basic test for topological sort

Co-authored-by: suncanghuai <[email protected]>
  • Loading branch information
suncanghuai and suncanghuai authored Nov 3, 2022
1 parent b22ccfc commit be05a46
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 0 deletions.
33 changes: 33 additions & 0 deletions benchmark/TopologicalSort_BM.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <benchmark/benchmark.h>
#include "CXXGraph.hpp"
#include "Utilities.hpp"


static void TopologicalSort_X(benchmark::State &state)
{
CXXGRAPH::Graph<int> g;
auto range_start = edges.begin();
auto range_end = edges.find(state.range(0));
std::unordered_map<unsigned long, CXXGRAPH::Edge<int> *> edgesX;
edgesX.insert(range_start, range_end);
for (auto e : edgesX)
{
g.addEdge(&(*e.second));
}
for (auto _ : state)
{
const auto &result = g.topologicalSort();
}
}
BENCHMARK(TopologicalSort_X)->RangeMultiplier(16)->Range((unsigned long)1, (unsigned long)1 << 16);

static void TopologicalSort_FromReadedCitHep(benchmark::State &state)
{
auto edgeSet = cit_graph_ptr->getEdgeSet();
for (auto _ : state)
{
const auto &result = cit_graph_ptr->topologicalSort();
}
}

BENCHMARK(TopologicalSort_FromReadedCitHep);
66 changes: 66 additions & 0 deletions include/Graph/Graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,14 @@ namespace CXXGRAPH
*/
virtual bool isStronglyConnectedGraph() const;

/**
* @brief This function sort nodes in topological order.
* Applicable for Directed Acyclic Graph
*
* @return a vector containing nodes in topological order
*/
virtual TopoSortResult<T> topologicalSort() const;

/**
* \brief
* This function performs performs the kosaraju algorthm on the graph to find the strongly connected components.
Expand Down Expand Up @@ -1971,6 +1979,64 @@ namespace CXXGRAPH
}
}

template <typename T>
TopoSortResult<T> Graph<T>::topologicalSort() const
{
TopoSortResult<T> result;
result.success = false;

if (!isDirectedGraph())
{
result.errorMessage = ERR_UNDIR_GRAPH;
return result;
}
else if (isCyclicDirectedGraphBFS())
{
result.errorMessage = ERR_CYCLIC_GRAPH;
return result;
}
else
{
const auto &adjMatrix = getAdjMatrix();
const auto &nodeSet = getNodeSet();
std::unordered_map<const Node<T> *, bool> visited;

std::function<void(const Node<T> *)> postorder_helper = [&postorder_helper, &adjMatrix, &visited, &result] (const Node<T> *curNode)
{
visited[curNode] = true;

if (adjMatrix.find(curNode) != adjMatrix.end())
{
for (const auto &edge : adjMatrix.at(curNode))
{
const auto &nextNode = edge.first;
if (false == visited[nextNode])
{
postorder_helper(nextNode);
}
}
}

result.nodesInTopoOrder.push_back(*curNode);
};

int numNodes = adjMatrix.size();
result.nodesInTopoOrder.reserve(numNodes);

for (const auto &node : nodeSet)
{
if (false == visited[node])
{
postorder_helper(node);
}
}

result.success = true;
std::reverse(result.nodesInTopoOrder.begin(), result.nodesInTopoOrder.end());
return result;
}
}

template <typename T>
std::vector<std::vector<Node<T>>> Graph<T>::kosaraju() const
{
Expand Down
2 changes: 2 additions & 0 deletions include/Utility/ConstString.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ namespace CXXGRAPH
//STRING ERROR CONST EXPRESSION
constexpr char ERR_NO_DIR_OR_UNDIR_EDGE[] = "Edge are neither Directed neither Undirected";
constexpr char ERR_DIR_GRAPH[] = "Graph is directed";
constexpr char ERR_UNDIR_GRAPH[] = "Graph is undirected";
constexpr char ERR_CYCLIC_GRAPH[] = "Graph is cyclic";
constexpr char ERR_NO_WEIGHTED_EDGE[] = "Edge are not Weighted";
constexpr char ERR_NEGATIVE_WEIGHTED_EDGE[] = "Edge negative Weighted";
constexpr char ERR_TARGET_NODE_NOT_REACHABLE[] = "Target Node not Reachable";
Expand Down
19 changes: 19 additions & 0 deletions include/Utility/Typedef.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,25 @@ namespace CXXGRAPH
};
typedef DialResult_struct DialResult;

/// Struct that contains the information about TopologicalSort's Algorithm results
template <typename T>
struct TopoSortResult_struct
{
bool success = false; // TRUE if the function does not return error, FALSE otherwise
std::string errorMessage = ""; //message of error
std::vector<Node<T>> nodesInTopoOrder = {}; //result a vector that contains the nodes in topological order (valid only if success is TRUE)

/*
TopoSortResult & operator=(TopoSortResult_struct && res)
: success(res.success),
errorMessage(move(res.errorMessage))
nodesInTopoOrder(move(res.nodesInTopoOrder))
{}
*/
};
template <typename T>
using TopoSortResult = TopoSortResult_struct<T>;

/// Struct that contains the information about the partitioning statistics


Expand Down
105 changes: 105 additions & 0 deletions test/TopologicalSortTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include "gtest/gtest.h"
#include "CXXGraph.hpp"
#include "Utility/Typedef.hpp"
#include "Utility/ConstString.hpp"

// topological sort test in a cyclic graph
TEST(TopologicalSortTest, test_1)
{
CXXGRAPH::Node<int> node1("1", 1);
CXXGRAPH::Node<int> node2("2", 2);
CXXGRAPH::Node<int> node3("3", 3);
std::pair<const CXXGRAPH::Node<int> *, const CXXGRAPH::Node<int> *> pairNode(&node1, &node2);
CXXGRAPH::DirectedWeightedEdge<int> edge1(1, pairNode, 1);
CXXGRAPH::DirectedWeightedEdge<int> edge2(2, node2, node3, 2);
CXXGRAPH::DirectedWeightedEdge<int> edge3(3, node3, node1, 3);
CXXGRAPH::T_EdgeSet<int> edgeSet;
edgeSet.insert(&edge1);
edgeSet.insert(&edge2);
edgeSet.insert(&edge3);
CXXGRAPH::Graph<int> graph(edgeSet);

CXXGRAPH::TopoSortResult<int> && res = graph.topologicalSort();
ASSERT_EQ(res.success, false);
ASSERT_EQ(res.errorMessage, CXXGRAPH::ERR_CYCLIC_GRAPH);
}

// topogical sort test in a undirected graph
TEST(TopologicalSortTest, test_2)
{
CXXGRAPH::Node<int> node1("1", 1);
CXXGRAPH::Node<int> node2("2", 2);
CXXGRAPH::Node<int> node3("3", 3);
CXXGRAPH::Node<int> node4("4", 4);
CXXGRAPH::Node<int> node5("5", 5);
std::pair<const CXXGRAPH::Node<int> *, const CXXGRAPH::Node<int> *> pairNode(&node1, &node2);
CXXGRAPH::UndirectedWeightedEdge<int> edge1(1, pairNode, 1);
CXXGRAPH::UndirectedWeightedEdge<int> edge2(2, node2, node3, 1);
CXXGRAPH::UndirectedWeightedEdge<int> edge3(3, node3, node4, 1);
CXXGRAPH::UndirectedWeightedEdge<int> edge4(3, node3, node5, 1);
CXXGRAPH::T_EdgeSet<int> edgeSet;
edgeSet.insert(&edge1);
edgeSet.insert(&edge2);
edgeSet.insert(&edge3);
edgeSet.insert(&edge4);
CXXGRAPH::Graph<int> graph(edgeSet);

CXXGRAPH::TopoSortResult<int> && res = graph.topologicalSort();
ASSERT_EQ(res.success, false);
ASSERT_EQ(res.errorMessage, CXXGRAPH::ERR_UNDIR_GRAPH);
}

// topogical sort test in a tiny graph
TEST(TopologicalSortTest, test_3)
{
CXXGRAPH::Node<int> node1("1", 1);
CXXGRAPH::Node<int> node2("2", 2);
CXXGRAPH::Node<int> node3("3", 3);
CXXGRAPH::Node<int> node4("4", 4);
CXXGRAPH::Node<int> node5("5", 5);
CXXGRAPH::Node<int> node6("6", 6);
CXXGRAPH::Node<int> node7("7", 7);
CXXGRAPH::Node<int> node8("8", 8);
std::pair<const CXXGRAPH::Node<int> *, const CXXGRAPH::Node<int> *> pairNode(&node1, &node2);
CXXGRAPH::DirectedWeightedEdge<int> edge1(1, pairNode, 1);
CXXGRAPH::DirectedWeightedEdge<int> edge2(2, node2, node3, 1);
CXXGRAPH::DirectedWeightedEdge<int> edge3(3, node2, node6, 6);
CXXGRAPH::DirectedWeightedEdge<int> edge4(3, node3, node4, 6);
CXXGRAPH::DirectedWeightedEdge<int> edge5(3, node3, node5, 6);
CXXGRAPH::DirectedWeightedEdge<int> edge6(3, node6, node7, 6);
CXXGRAPH::DirectedWeightedEdge<int> edge7(3, node8, node6, 6);
CXXGRAPH::DirectedWeightedEdge<int> edge8(3, node8, node7, 6);
CXXGRAPH::T_EdgeSet<int> 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<int> graph(edgeSet);

CXXGRAPH::TopoSortResult<int> && res = graph.topologicalSort();
ASSERT_EQ(res.success, true);
ASSERT_TRUE(res.errorMessage.empty());
ASSERT_EQ(res.nodesInTopoOrder.size(), 8);

// check topological order of nodes
std::unordered_map<unsigned long, int> nodeToOrder;
for (int i = 0; i < res.nodesInTopoOrder.size(); ++i)
{
nodeToOrder[res.nodesInTopoOrder[i].getId()] = i;
}

for (const auto & edge : edgeSet)
{
auto node_id1 = edge->getNodePair().first->getId();
auto node_id2 = edge->getNodePair().second->getId();

ASSERT_TRUE(nodeToOrder.count(node_id1) && nodeToOrder.count(node_id2));
ASSERT_LT(nodeToOrder[node_id1], nodeToOrder[node_id2]);
}
}

// more test cases to be needed

0 comments on commit be05a46

Please sign in to comment.