From ccf3f2cb8c41baf0c467dc3cac901fb773678daa Mon Sep 17 00:00:00 2001 From: Pin Loon Lee Date: Mon, 8 Dec 2025 22:58:35 +0800 Subject: [PATCH 1/3] src: generic: added disjoint_union_set --- .../disjoint_union_set/disjoint_union_set.hpp | 57 +++++++++++++++++++ src/generic/CMakeLists.txt | 1 + src/generic/disjoint_union_set/CMakeLists.txt | 3 + .../disjoint_union_set/disjoint_union_set.cpp | 43 ++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 include/algorithm/generic/disjoint_union_set/disjoint_union_set.hpp create mode 100644 src/generic/disjoint_union_set/CMakeLists.txt create mode 100644 src/generic/disjoint_union_set/disjoint_union_set.cpp diff --git a/include/algorithm/generic/disjoint_union_set/disjoint_union_set.hpp b/include/algorithm/generic/disjoint_union_set/disjoint_union_set.hpp new file mode 100644 index 0000000..5bcc42c --- /dev/null +++ b/include/algorithm/generic/disjoint_union_set/disjoint_union_set.hpp @@ -0,0 +1,57 @@ +/* + * @file disjoint_union_set.hpp + * + * Created on: Dec 08, 2025 22:41 + * @brief Implementation of the Disjoint Set Union (DSU) or Union-Find data structure. + * + * @details This data structure keeps track of a set of elements partitioned into a + * number of disjoint (non-overlapping) subsets. It provides two main + * operations: finding the set to which an element belongs and merging two sets. + * + * Copyright (c) 2025 Pin Loon Lee (pllee4) + */ + +#pragma once + +#include + +namespace pllee4::generic { +/** + * @brief A class representing the Disjoint Set Union (DSU) data structure. + * @details Also known as Union-Find, this data structure manages a collection of + * disjoint sets. It is highly optimized for finding which set an element + * belongs to and for merging two sets, using path compression and union by + * rank. + */ +class DisjointUnionSets { + public: + /** + * @brief Constructs a new Disjoint Union Sets object. + * @param n The initial number of elements, each in its own set. + */ + explicit DisjointUnionSets(int n); + + /** + * @brief Finds the representative (or root) of the set containing element i. + * @details This method uses path compression to flatten the tree structure, + * speeding up future find operations. + * @param i The element to find. + * @return The representative of the set containing i. + */ + int Find(int i); + + /** + * @brief Merges the sets containing elements x and y. + * @details This method uses union by rank to keep the tree structures flat. + * If the two elements are already in the same set, no action is taken. + * @param x An element in the first set. + * @param y An element in the second set. + */ + void UnionSets(int x, int y); + + private: + std::vector parent_; // parent_[i] stores the parent of element i. + std::vector rank_; // rank_[i] stores the rank (an upper bound on the + // height) of the tree rooted at i. +}; +} // namespace pllee4::generic \ No newline at end of file diff --git a/src/generic/CMakeLists.txt b/src/generic/CMakeLists.txt index 9d664b4..8f90e45 100644 --- a/src/generic/CMakeLists.txt +++ b/src/generic/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(common) +add_subdirectory(disjoint_union_set) add_subdirectory(digital_filter) add_subdirectory(polynomial) \ No newline at end of file diff --git a/src/generic/disjoint_union_set/CMakeLists.txt b/src/generic/disjoint_union_set/CMakeLists.txt new file mode 100644 index 0000000..2cb5080 --- /dev/null +++ b/src/generic/disjoint_union_set/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(disjoint_union_set ${CMAKE_CURRENT_SOURCE_DIR}/disjoint_union_set.cpp) +target_include_directories(disjoint_union_set PUBLIC +$) \ No newline at end of file diff --git a/src/generic/disjoint_union_set/disjoint_union_set.cpp b/src/generic/disjoint_union_set/disjoint_union_set.cpp new file mode 100644 index 0000000..cf12c03 --- /dev/null +++ b/src/generic/disjoint_union_set/disjoint_union_set.cpp @@ -0,0 +1,43 @@ +/* + * disjoint_union_set.cpp + * + * Created on: Dec 08, 2025 22:43 + * Description: + * + * Copyright (c) 2025 Pin Loon Lee (pllee4) + */ + +#include "algorithm/generic/disjoint_union_set/disjoint_union_set.hpp" + +#include + +namespace pllee4::generic { +DisjointUnionSets::DisjointUnionSets(int n) { + rank_.resize(n, 0); + parent_.resize(n); + std::iota(parent_.begin(), parent_.end(), 0); +} + +int DisjointUnionSets::Find(int i) { + if (parent_[i] != i) { + parent_[i] = Find(parent_[i]); + } + return parent_[i]; +} + +void DisjointUnionSets::UnionSets(int x, int y) { + int xRoot = Find(x); + int yRoot = Find(y); + if (xRoot == yRoot) { + return; + } + if (rank_[xRoot] < rank_[yRoot]) { + parent_[xRoot] = yRoot; + } else if (rank_[yRoot] < rank_[xRoot]) { + parent_[yRoot] = xRoot; + } else { + parent_[yRoot] = xRoot; + ++rank_[xRoot]; + } +} +} // namespace pllee4::generic From 22175422f66a7712c143701ad8ffb54389709b91 Mon Sep 17 00:00:00 2001 From: Pin Loon Lee Date: Tue, 9 Dec 2025 22:32:21 +0800 Subject: [PATCH 2/3] src: generic: added test for disjoint_union_set --- src/generic/disjoint_union_set/CMakeLists.txt | 18 ++- .../test/disjoint_union_set_test.cpp | 152 ++++++++++++++++++ 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/generic/disjoint_union_set/test/disjoint_union_set_test.cpp diff --git a/src/generic/disjoint_union_set/CMakeLists.txt b/src/generic/disjoint_union_set/CMakeLists.txt index 2cb5080..0a2de23 100644 --- a/src/generic/disjoint_union_set/CMakeLists.txt +++ b/src/generic/disjoint_union_set/CMakeLists.txt @@ -1,3 +1,19 @@ add_library(disjoint_union_set ${CMAKE_CURRENT_SOURCE_DIR}/disjoint_union_set.cpp) target_include_directories(disjoint_union_set PUBLIC -$) \ No newline at end of file +$) + +if(ALGO_BUILD_TEST) +# test for Polynomial +mark_as_advanced( + BUILD_GTEST BUILD_SHARED_LIBS + gtest_build_samples gtest_build_tests + gtest_disable_pthreads gtest_force_shared_crt gtest_hide_internal_symbols +) + +add_executable(disjoint_union_set_test + ${CMAKE_CURRENT_SOURCE_DIR}/test/disjoint_union_set_test.cpp +) + +target_link_libraries(disjoint_union_set_test PRIVATE gtest_main disjoint_union_set) +gtest_discover_tests(disjoint_union_set_test XML_OUTPUT_DIR ${PROJECT_BINARY_DIR}/test-reports) +endif() \ No newline at end of file diff --git a/src/generic/disjoint_union_set/test/disjoint_union_set_test.cpp b/src/generic/disjoint_union_set/test/disjoint_union_set_test.cpp new file mode 100644 index 0000000..9bee6c7 --- /dev/null +++ b/src/generic/disjoint_union_set/test/disjoint_union_set_test.cpp @@ -0,0 +1,152 @@ +/* + * disjoint_union_set_test.cpp + * + * Created on: Dec 09, 2025 22:08 + * Description: + * + * Copyright (c) 2025 Pin Loon Lee (pllee4) + */ + +#include "algorithm/generic/disjoint_union_set/disjoint_union_set.hpp" + +#include "gtest/gtest.h" + +using namespace pllee4::generic; + +TEST(DisjointUnionSetsTest, InitialState) { + DisjointUnionSets dsu(5); + + for (auto i = 0; i < 5; ++i) { + EXPECT_EQ(dsu.Find(i), i) + << "Element " << i << " should be its own parent initially"; + } +} + +TEST(DisjointUnionSetsTest, UnionTwoElements) { + DisjointUnionSets dsu(5); + + dsu.UnionSets(0, 1); + + EXPECT_EQ(dsu.Find(0), dsu.Find(1)) + << "Elements 0 and 1 should have the same root"; +} + +TEST(DisjointUnionSetsTest, UnionChain) { + DisjointUnionSets dsu(5); + + dsu.UnionSets(0, 1); + dsu.UnionSets(1, 2); + dsu.UnionSets(2, 3); + + const auto root = dsu.Find(0); + EXPECT_EQ(dsu.Find(1), root); + EXPECT_EQ(dsu.Find(2), root); + EXPECT_EQ(dsu.Find(3), root); + EXPECT_NE(dsu.Find(4), root) << "Element 4 should not be in the same set"; +} + +TEST(DisjointUnionSetsTest, PathCompression) { + DisjointUnionSets dsu(5); + + dsu.UnionSets(0, 1); + dsu.UnionSets(1, 2); + dsu.UnionSets(2, 3); + + // After first Find, path should be compressed + const auto root = dsu.Find(3); + EXPECT_EQ(dsu.Find(0), root); + + // All elements should now point directly to root (path compression) + EXPECT_EQ(dsu.Find(1), root); + EXPECT_EQ(dsu.Find(2), root); +} + +TEST(DisjointUnionSetsTest, UnionByRank) { + DisjointUnionSets dsu(10); + + // Create two trees of different ranks + dsu.UnionSets(0, 1); + dsu.UnionSets(2, 3); + dsu.UnionSets(3, 4); + + EXPECT_NE(dsu.Find(0), dsu.Find(2)); + + // Union the two trees + dsu.UnionSets(0, 2); + + // The tree with higher rank should become the root + EXPECT_EQ(dsu.Find(4), dsu.Find(0)); +} + +TEST(DisjointUnionSetsTest, SelfUnion) { + DisjointUnionSets dsu(5); + + dsu.UnionSets(2, 2); + + EXPECT_EQ(dsu.Find(2), 2) << "Self-union should not change the parent"; +} + +// Test: Multiple disjoint sets +TEST(DisjointUnionSetsTest, MultipleDisjointSets) { + DisjointUnionSets dsu(10); + + dsu.UnionSets(0, 1); + dsu.UnionSets(2, 3); + dsu.UnionSets(4, 5); + + // Check that different sets have different roots + EXPECT_EQ(dsu.Find(0), dsu.Find(1)); + EXPECT_EQ(dsu.Find(2), dsu.Find(3)); + EXPECT_EQ(dsu.Find(4), dsu.Find(5)); + + EXPECT_NE(dsu.Find(0), dsu.Find(2)); + EXPECT_NE(dsu.Find(2), dsu.Find(4)); + EXPECT_NE(dsu.Find(0), dsu.Find(4)); +} + +TEST(DisjointUnionSetsTest, IdempotentUnions) { + DisjointUnionSets dsu(5); + + dsu.UnionSets(0, 1); + const auto root1 = dsu.Find(0); + + dsu.UnionSets(0, 1); + dsu.UnionSets(1, 0); + + EXPECT_EQ(dsu.Find(0), root1) + << "Repeated unions should not change the structure"; +} + +TEST(DisjointUnionSetsTest, LargeSet) { + DisjointUnionSets dsu(1000); + + // Union all elements into one set + for (auto i = 1; i < 1000; ++i) { + dsu.UnionSets(0, i); + } + + const auto root = dsu.Find(0); + for (int i = 1; i < 1000; ++i) { + EXPECT_EQ(dsu.Find(i), root) << "All elements should be in the same set"; + } +} + +TEST(DisjointUnionSetsTest, AlternatingUnions) { + DisjointUnionSets dsu(8); + + dsu.UnionSets(0, 2); + dsu.UnionSets(1, 3); + dsu.UnionSets(4, 6); + dsu.UnionSets(5, 7); + + dsu.UnionSets(0, 4); + dsu.UnionSets(1, 5); + + dsu.UnionSets(0, 1); + + // All should be in the same set now + const auto root = dsu.Find(0); + for (int i = 1; i < 8; ++i) { + EXPECT_EQ(dsu.Find(i), root); + } +} \ No newline at end of file From 0eeb59283ba8fff52ae710cb4123217fa0bc193b Mon Sep 17 00:00:00 2001 From: Pin Loon Lee Date: Sat, 20 Dec 2025 20:52:07 +0800 Subject: [PATCH 3/3] src: generic: disjoint_union_set_test: improve code coverage --- .../test/disjoint_union_set_test.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/generic/disjoint_union_set/test/disjoint_union_set_test.cpp b/src/generic/disjoint_union_set/test/disjoint_union_set_test.cpp index 9bee6c7..1b2a2a5 100644 --- a/src/generic/disjoint_union_set/test/disjoint_union_set_test.cpp +++ b/src/generic/disjoint_union_set/test/disjoint_union_set_test.cpp @@ -78,6 +78,18 @@ TEST(DisjointUnionSetsTest, UnionByRank) { EXPECT_EQ(dsu.Find(4), dsu.Find(0)); } +TEST(DisjointUnionSetsTest, UnionLowerRankToHigherRank) { + DisjointUnionSets dsu(6); + + dsu.UnionSets(0, 1); + dsu.UnionSets(2, 3); + dsu.UnionSets(0, 2); + + dsu.UnionSets(4, 0); + + EXPECT_EQ(dsu.Find(4), dsu.Find(0)); +} + TEST(DisjointUnionSetsTest, SelfUnion) { DisjointUnionSets dsu(5);