From b540de2aba67c2c90088500dd4366abf1a285aa2 Mon Sep 17 00:00:00 2001 From: Weidong Lian Date: Tue, 10 Dec 2024 00:42:50 +0100 Subject: [PATCH] add directed acyclic graph to handle module dependencies --- algorithms/CMakeLists.txt | 10 +- algorithms/tree/directed_acyclic_graph.cpp | 106 +++++++++++++++++++++ algorithms/tree/directed_graph.cpp | 30 ------ algorithms/tree/tree_manipulate.cpp | 2 +- algorithms/tree/visit_tree.cpp | 2 +- 5 files changed, 116 insertions(+), 34 deletions(-) create mode 100644 algorithms/tree/directed_acyclic_graph.cpp delete mode 100644 algorithms/tree/directed_graph.cpp diff --git a/algorithms/CMakeLists.txt b/algorithms/CMakeLists.txt index 6edc160c..bc2d90b3 100644 --- a/algorithms/CMakeLists.txt +++ b/algorithms/CMakeLists.txt @@ -28,7 +28,7 @@ add_executable(test-algorithms tree/ordered_map.cpp tree/visit_tree.cpp tree/tree_manipulate.cpp - tree/directed_graph.cpp + tree/directed_acyclic_graph.cpp tree/flatten_bst_into_linked_list.cpp hash_table/separate_chainning.cpp set/set_key.cpp @@ -41,7 +41,13 @@ if(OpenMP_CXX_FOUND) set(LINK_LIB_OPENMP OpenMP::OpenMP_CXX) endif() -target_link_libraries(test-algorithms Threads::Threads Catch2::Catch2WithMain rapidcheck simple_svg ${LINK_LIB_OPENMP}) +target_link_libraries(test-algorithms + Threads::Threads + Catch2::Catch2WithMain + rapidcheck + simple_svg + absl::inlined_vector + ${LINK_LIB_OPENMP}) target_include_directories(test-algorithms PRIVATE "$") add_test(NAME test-algorithms COMMAND test-algorithms) diff --git a/algorithms/tree/directed_acyclic_graph.cpp b/algorithms/tree/directed_acyclic_graph.cpp new file mode 100644 index 00000000..c0c4a7c6 --- /dev/null +++ b/algorithms/tree/directed_acyclic_graph.cpp @@ -0,0 +1,106 @@ +#include +#include + +#include "absl/container/inlined_vector.h" +#include "base.hpp" + +namespace { + +template +struct node { + T val; // node value + int degree; // number of edges connected to parent node + absl::InlinedVector children; // children nodes +}; + +template +class graph { +public: + graph() : nodes() {} + + void add_node(const T& val) { + if (!nodes.contains(val)) { + auto& n = nodes[val]; + n.val = val; + n.degree = 0; + } + } + + void add_edge(const T& from, const T& to) { + add_node(from); + add_node(to); + nodes[from].children.push_back(to); + nodes[to].degree++; + } + + // Topological sort returns nodes in order of their level. + // Throws an exception if the graph has a cycle. + // Otherwise, returns nodes in topological order. + // Nodes are sorted by value per level for deterministic results. + std::vector topological_sort() const { + std::vector result; + std::map current_degrees; + std::queue queue; + // Group nodes by their level + for (const auto& [val, n] : nodes) { + current_degrees[val] = n.degree; + if (n.degree == 0) { + queue.push(val); + } + } + + absl::InlinedVector sorted_children; + while (!queue.empty()) { + auto val = queue.front(); + queue.pop(); + result.push_back(val); + + sorted_children.clear(); + for (const auto& adj : nodes.at(val).children) { + if (--current_degrees[adj] == 0) { + sorted_children.push_back(adj); + } + } + std::sort(sorted_children.begin(), sorted_children.end()); + for (const auto& adj : sorted_children) { + queue.push(adj); + } + } + + if (result.size() != nodes.size()) { + throw std::runtime_error("graph has cycle."); + } + + return result; + } + +private: + std::map> nodes; +}; + +} // namespace + +TEST_CASE("dag_has_cycle", "[tree]") { + graph g; + g.add_edge(1, 2); + g.add_edge(2, 3); + g.add_edge(3, 4); + g.add_edge(4, 1); + CHECK_THROWS(g.topological_sort()); +} + +TEST_CASE("dag_topological_sort", "[tree]") { + graph g; + g.add_edge(1, 2); + g.add_edge(2, 3); + g.add_edge(3, 4); + g.add_node(5); // isolated node + g.add_edge(7, 6); + REQUIRE(g.topological_sort() == std::vector({1, 5, 7, 2, 6, 3, 4})); +} + +TEST_CASE("dag_topological_sort_single_node", "[tree]") { + graph g; + g.add_node(5); // isolated node + REQUIRE(g.topological_sort() == std::vector({5})); +} \ No newline at end of file diff --git a/algorithms/tree/directed_graph.cpp b/algorithms/tree/directed_graph.cpp deleted file mode 100644 index 8c45b238..00000000 --- a/algorithms/tree/directed_graph.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include -#include - -#include "base.hpp" - -struct dg_node { - dg_node(int value) : value_(value), neighbours() {} - - int value_; - // we do not own them, they are created in higher level and - // should not be deleted when the current dg_node is destructed. - std::vector neighbours; -}; - -struct dg_edge { - int from; - int to; -}; - -class dg_tree { -public: - dg_tree() : nodes(), edges() {} - -private: - std::vector nodes; - std::vector edges; -}; - -TEST_CASE("directed_graph", "[tree]") {} \ No newline at end of file diff --git a/algorithms/tree/tree_manipulate.cpp b/algorithms/tree/tree_manipulate.cpp index ed6ca853..49b74fc4 100644 --- a/algorithms/tree/tree_manipulate.cpp +++ b/algorithms/tree/tree_manipulate.cpp @@ -9,7 +9,7 @@ #include "base.hpp" struct tree_node { - tree_node(int value) : value_(value), left_(nullptr), right_(nullptr) {} + explicit tree_node(int value) : value_(value), left_(nullptr), right_(nullptr) {} ~tree_node() { delete left_; delete right_; diff --git a/algorithms/tree/visit_tree.cpp b/algorithms/tree/visit_tree.cpp index de037267..97b45c2e 100644 --- a/algorithms/tree/visit_tree.cpp +++ b/algorithms/tree/visit_tree.cpp @@ -6,7 +6,7 @@ namespace { class vt_node { public: - vt_node(std::string name) : name_(std::move(name)), left_(nullptr), right_(nullptr) {} + explicit vt_node(std::string name) : name_(std::move(name)), left_(nullptr), right_(nullptr) {} ~vt_node() { delete left_;