Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 <vector>

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<int> parent_; // parent_[i] stores the parent of element i.
std::vector<int> rank_; // rank_[i] stores the rank (an upper bound on the
// height) of the tree rooted at i.
};
} // namespace pllee4::generic
1 change: 1 addition & 0 deletions src/generic/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
add_subdirectory(common)
add_subdirectory(disjoint_union_set)
add_subdirectory(digital_filter)
add_subdirectory(polynomial)
19 changes: 19 additions & 0 deletions src/generic/disjoint_union_set/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
add_library(disjoint_union_set ${CMAKE_CURRENT_SOURCE_DIR}/disjoint_union_set.cpp)
target_include_directories(disjoint_union_set PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)

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()
43 changes: 43 additions & 0 deletions src/generic/disjoint_union_set/disjoint_union_set.cpp
Original file line number Diff line number Diff line change
@@ -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 <numeric>

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
164 changes: 164 additions & 0 deletions src/generic/disjoint_union_set/test/disjoint_union_set_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* 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, 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);

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);
}
}