Skip to content

Commit dc74c52

Browse files
authored
Merge pull request #17 from pllee4/feature-add_disjoint_union_set
Feature add disjoint union set
2 parents f40c0e9 + 0eeb592 commit dc74c52

5 files changed

Lines changed: 284 additions & 0 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* @file disjoint_union_set.hpp
3+
*
4+
* Created on: Dec 08, 2025 22:41
5+
* @brief Implementation of the Disjoint Set Union (DSU) or Union-Find data structure.
6+
*
7+
* @details This data structure keeps track of a set of elements partitioned into a
8+
* number of disjoint (non-overlapping) subsets. It provides two main
9+
* operations: finding the set to which an element belongs and merging two sets.
10+
*
11+
* Copyright (c) 2025 Pin Loon Lee (pllee4)
12+
*/
13+
14+
#pragma once
15+
16+
#include <vector>
17+
18+
namespace pllee4::generic {
19+
/**
20+
* @brief A class representing the Disjoint Set Union (DSU) data structure.
21+
* @details Also known as Union-Find, this data structure manages a collection of
22+
* disjoint sets. It is highly optimized for finding which set an element
23+
* belongs to and for merging two sets, using path compression and union by
24+
* rank.
25+
*/
26+
class DisjointUnionSets {
27+
public:
28+
/**
29+
* @brief Constructs a new Disjoint Union Sets object.
30+
* @param n The initial number of elements, each in its own set.
31+
*/
32+
explicit DisjointUnionSets(int n);
33+
34+
/**
35+
* @brief Finds the representative (or root) of the set containing element i.
36+
* @details This method uses path compression to flatten the tree structure,
37+
* speeding up future find operations.
38+
* @param i The element to find.
39+
* @return The representative of the set containing i.
40+
*/
41+
int Find(int i);
42+
43+
/**
44+
* @brief Merges the sets containing elements x and y.
45+
* @details This method uses union by rank to keep the tree structures flat.
46+
* If the two elements are already in the same set, no action is taken.
47+
* @param x An element in the first set.
48+
* @param y An element in the second set.
49+
*/
50+
void UnionSets(int x, int y);
51+
52+
private:
53+
std::vector<int> parent_; // parent_[i] stores the parent of element i.
54+
std::vector<int> rank_; // rank_[i] stores the rank (an upper bound on the
55+
// height) of the tree rooted at i.
56+
};
57+
} // namespace pllee4::generic

src/generic/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
add_subdirectory(common)
2+
add_subdirectory(disjoint_union_set)
23
add_subdirectory(digital_filter)
34
add_subdirectory(polynomial)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
add_library(disjoint_union_set ${CMAKE_CURRENT_SOURCE_DIR}/disjoint_union_set.cpp)
2+
target_include_directories(disjoint_union_set PUBLIC
3+
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
4+
5+
if(ALGO_BUILD_TEST)
6+
# test for Polynomial
7+
mark_as_advanced(
8+
BUILD_GTEST BUILD_SHARED_LIBS
9+
gtest_build_samples gtest_build_tests
10+
gtest_disable_pthreads gtest_force_shared_crt gtest_hide_internal_symbols
11+
)
12+
13+
add_executable(disjoint_union_set_test
14+
${CMAKE_CURRENT_SOURCE_DIR}/test/disjoint_union_set_test.cpp
15+
)
16+
17+
target_link_libraries(disjoint_union_set_test PRIVATE gtest_main disjoint_union_set)
18+
gtest_discover_tests(disjoint_union_set_test XML_OUTPUT_DIR ${PROJECT_BINARY_DIR}/test-reports)
19+
endif()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* disjoint_union_set.cpp
3+
*
4+
* Created on: Dec 08, 2025 22:43
5+
* Description:
6+
*
7+
* Copyright (c) 2025 Pin Loon Lee (pllee4)
8+
*/
9+
10+
#include "algorithm/generic/disjoint_union_set/disjoint_union_set.hpp"
11+
12+
#include <numeric>
13+
14+
namespace pllee4::generic {
15+
DisjointUnionSets::DisjointUnionSets(int n) {
16+
rank_.resize(n, 0);
17+
parent_.resize(n);
18+
std::iota(parent_.begin(), parent_.end(), 0);
19+
}
20+
21+
int DisjointUnionSets::Find(int i) {
22+
if (parent_[i] != i) {
23+
parent_[i] = Find(parent_[i]);
24+
}
25+
return parent_[i];
26+
}
27+
28+
void DisjointUnionSets::UnionSets(int x, int y) {
29+
int xRoot = Find(x);
30+
int yRoot = Find(y);
31+
if (xRoot == yRoot) {
32+
return;
33+
}
34+
if (rank_[xRoot] < rank_[yRoot]) {
35+
parent_[xRoot] = yRoot;
36+
} else if (rank_[yRoot] < rank_[xRoot]) {
37+
parent_[yRoot] = xRoot;
38+
} else {
39+
parent_[yRoot] = xRoot;
40+
++rank_[xRoot];
41+
}
42+
}
43+
} // namespace pllee4::generic
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* disjoint_union_set_test.cpp
3+
*
4+
* Created on: Dec 09, 2025 22:08
5+
* Description:
6+
*
7+
* Copyright (c) 2025 Pin Loon Lee (pllee4)
8+
*/
9+
10+
#include "algorithm/generic/disjoint_union_set/disjoint_union_set.hpp"
11+
12+
#include "gtest/gtest.h"
13+
14+
using namespace pllee4::generic;
15+
16+
TEST(DisjointUnionSetsTest, InitialState) {
17+
DisjointUnionSets dsu(5);
18+
19+
for (auto i = 0; i < 5; ++i) {
20+
EXPECT_EQ(dsu.Find(i), i)
21+
<< "Element " << i << " should be its own parent initially";
22+
}
23+
}
24+
25+
TEST(DisjointUnionSetsTest, UnionTwoElements) {
26+
DisjointUnionSets dsu(5);
27+
28+
dsu.UnionSets(0, 1);
29+
30+
EXPECT_EQ(dsu.Find(0), dsu.Find(1))
31+
<< "Elements 0 and 1 should have the same root";
32+
}
33+
34+
TEST(DisjointUnionSetsTest, UnionChain) {
35+
DisjointUnionSets dsu(5);
36+
37+
dsu.UnionSets(0, 1);
38+
dsu.UnionSets(1, 2);
39+
dsu.UnionSets(2, 3);
40+
41+
const auto root = dsu.Find(0);
42+
EXPECT_EQ(dsu.Find(1), root);
43+
EXPECT_EQ(dsu.Find(2), root);
44+
EXPECT_EQ(dsu.Find(3), root);
45+
EXPECT_NE(dsu.Find(4), root) << "Element 4 should not be in the same set";
46+
}
47+
48+
TEST(DisjointUnionSetsTest, PathCompression) {
49+
DisjointUnionSets dsu(5);
50+
51+
dsu.UnionSets(0, 1);
52+
dsu.UnionSets(1, 2);
53+
dsu.UnionSets(2, 3);
54+
55+
// After first Find, path should be compressed
56+
const auto root = dsu.Find(3);
57+
EXPECT_EQ(dsu.Find(0), root);
58+
59+
// All elements should now point directly to root (path compression)
60+
EXPECT_EQ(dsu.Find(1), root);
61+
EXPECT_EQ(dsu.Find(2), root);
62+
}
63+
64+
TEST(DisjointUnionSetsTest, UnionByRank) {
65+
DisjointUnionSets dsu(10);
66+
67+
// Create two trees of different ranks
68+
dsu.UnionSets(0, 1);
69+
dsu.UnionSets(2, 3);
70+
dsu.UnionSets(3, 4);
71+
72+
EXPECT_NE(dsu.Find(0), dsu.Find(2));
73+
74+
// Union the two trees
75+
dsu.UnionSets(0, 2);
76+
77+
// The tree with higher rank should become the root
78+
EXPECT_EQ(dsu.Find(4), dsu.Find(0));
79+
}
80+
81+
TEST(DisjointUnionSetsTest, UnionLowerRankToHigherRank) {
82+
DisjointUnionSets dsu(6);
83+
84+
dsu.UnionSets(0, 1);
85+
dsu.UnionSets(2, 3);
86+
dsu.UnionSets(0, 2);
87+
88+
dsu.UnionSets(4, 0);
89+
90+
EXPECT_EQ(dsu.Find(4), dsu.Find(0));
91+
}
92+
93+
TEST(DisjointUnionSetsTest, SelfUnion) {
94+
DisjointUnionSets dsu(5);
95+
96+
dsu.UnionSets(2, 2);
97+
98+
EXPECT_EQ(dsu.Find(2), 2) << "Self-union should not change the parent";
99+
}
100+
101+
// Test: Multiple disjoint sets
102+
TEST(DisjointUnionSetsTest, MultipleDisjointSets) {
103+
DisjointUnionSets dsu(10);
104+
105+
dsu.UnionSets(0, 1);
106+
dsu.UnionSets(2, 3);
107+
dsu.UnionSets(4, 5);
108+
109+
// Check that different sets have different roots
110+
EXPECT_EQ(dsu.Find(0), dsu.Find(1));
111+
EXPECT_EQ(dsu.Find(2), dsu.Find(3));
112+
EXPECT_EQ(dsu.Find(4), dsu.Find(5));
113+
114+
EXPECT_NE(dsu.Find(0), dsu.Find(2));
115+
EXPECT_NE(dsu.Find(2), dsu.Find(4));
116+
EXPECT_NE(dsu.Find(0), dsu.Find(4));
117+
}
118+
119+
TEST(DisjointUnionSetsTest, IdempotentUnions) {
120+
DisjointUnionSets dsu(5);
121+
122+
dsu.UnionSets(0, 1);
123+
const auto root1 = dsu.Find(0);
124+
125+
dsu.UnionSets(0, 1);
126+
dsu.UnionSets(1, 0);
127+
128+
EXPECT_EQ(dsu.Find(0), root1)
129+
<< "Repeated unions should not change the structure";
130+
}
131+
132+
TEST(DisjointUnionSetsTest, LargeSet) {
133+
DisjointUnionSets dsu(1000);
134+
135+
// Union all elements into one set
136+
for (auto i = 1; i < 1000; ++i) {
137+
dsu.UnionSets(0, i);
138+
}
139+
140+
const auto root = dsu.Find(0);
141+
for (int i = 1; i < 1000; ++i) {
142+
EXPECT_EQ(dsu.Find(i), root) << "All elements should be in the same set";
143+
}
144+
}
145+
146+
TEST(DisjointUnionSetsTest, AlternatingUnions) {
147+
DisjointUnionSets dsu(8);
148+
149+
dsu.UnionSets(0, 2);
150+
dsu.UnionSets(1, 3);
151+
dsu.UnionSets(4, 6);
152+
dsu.UnionSets(5, 7);
153+
154+
dsu.UnionSets(0, 4);
155+
dsu.UnionSets(1, 5);
156+
157+
dsu.UnionSets(0, 1);
158+
159+
// All should be in the same set now
160+
const auto root = dsu.Find(0);
161+
for (int i = 1; i < 8; ++i) {
162+
EXPECT_EQ(dsu.Find(i), root);
163+
}
164+
}

0 commit comments

Comments
 (0)