Skip to content

Commit 1393f5e

Browse files
Added-> DisjointUnion Set by Size (#6514)
* Added-> DisjointUnion Set by Size * Add tests for DisjointSetUnionBySize --------- Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
1 parent d09180e commit 1393f5e

File tree

2 files changed

+229
-0
lines changed

2 files changed

+229
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.thealgorithms.datastructures.disjointsetunion;
2+
3+
/**
4+
* Disjoint Set Union (DSU) with Union by Size.
5+
* This data structure tracks a set of elements partitioned into disjoint (non-overlapping) subsets.
6+
* It supports two primary operations efficiently:
7+
*
8+
* <ul>
9+
* <li>Find: Determine which subset a particular element belongs to.</li>
10+
* <li>Union: Merge two subsets into a single subset using union by size.</li>
11+
* </ul>
12+
*
13+
* Union by size always attaches the smaller tree under the root of the larger tree.
14+
* This helps keep the tree shallow, improving the efficiency of find operations.
15+
*
16+
* @see <a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure">Disjoint Set Union (Wikipedia)</a>
17+
*/
18+
public class DisjointSetUnionBySize<T> {
19+
/**
20+
* Node class for DSU by size.
21+
* Each node keeps track of its parent and the size of the set it represents.
22+
*/
23+
public static class Node<T> {
24+
public T value;
25+
public Node<T> parent;
26+
public int size; // size of the set
27+
28+
public Node(T value) {
29+
this.value = value;
30+
this.parent = this;
31+
this.size = 1; // initially, the set size is 1
32+
}
33+
}
34+
35+
/**
36+
* Creates a new disjoint set containing the single specified element.
37+
* @param value the element to be placed in a new singleton set
38+
* @return a node representing the new set
39+
*/
40+
public Node<T> makeSet(final T value) {
41+
return new Node<>(value);
42+
}
43+
44+
/**
45+
* Finds and returns the representative (root) of the set containing the given node.
46+
* This method applies path compression to flatten the tree structure for future efficiency.
47+
* @param node the node whose set representative is to be found
48+
* @return the representative (root) node of the set
49+
*/
50+
public Node<T> findSet(Node<T> node) {
51+
if (node != node.parent) {
52+
node.parent = findSet(node.parent); // path compression
53+
}
54+
return node.parent;
55+
}
56+
57+
/**
58+
* Merges the sets containing the two given nodes using union by size.
59+
* The root of the smaller set is attached to the root of the larger set.
60+
* @param x a node in the first set
61+
* @param y a node in the second set
62+
*/
63+
public void unionSets(Node<T> x, Node<T> y) {
64+
Node<T> rootX = findSet(x);
65+
Node<T> rootY = findSet(y);
66+
67+
if (rootX == rootY) {
68+
return; // They are already in the same set
69+
}
70+
// Union by size: attach smaller tree under the larger one
71+
if (rootX.size < rootY.size) {
72+
rootX.parent = rootY;
73+
rootY.size += rootX.size; // update size
74+
} else {
75+
rootY.parent = rootX;
76+
rootX.size += rootY.size; // update size
77+
}
78+
}
79+
}
80+
// This implementation uses union by size instead of union by rank.
81+
// The size field tracks the number of elements in each set.
82+
// When two sets are merged, the smaller set is always attached to the larger set's root.
83+
// This helps keep the tree shallow and improves the efficiency of find operations.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package com.thealgorithms.datastructures.disjointsetunion;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
5+
import static org.junit.jupiter.api.Assertions.assertNotNull;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
public class DisjointSetUnionBySizeTest {
10+
11+
@Test
12+
public void testMakeSet() {
13+
DisjointSetUnionBySize<Integer> dsu = new DisjointSetUnionBySize<>();
14+
DisjointSetUnionBySize.Node<Integer> node = dsu.makeSet(1);
15+
assertNotNull(node);
16+
assertEquals(node, node.parent);
17+
assertEquals(1, node.size);
18+
}
19+
20+
@Test
21+
public void testUnionFindSet() {
22+
DisjointSetUnionBySize<Integer> dsu = new DisjointSetUnionBySize<>();
23+
DisjointSetUnionBySize.Node<Integer> node1 = dsu.makeSet(1);
24+
DisjointSetUnionBySize.Node<Integer> node2 = dsu.makeSet(2);
25+
DisjointSetUnionBySize.Node<Integer> node3 = dsu.makeSet(3);
26+
DisjointSetUnionBySize.Node<Integer> node4 = dsu.makeSet(4);
27+
28+
dsu.unionSets(node1, node2);
29+
dsu.unionSets(node3, node2);
30+
dsu.unionSets(node3, node4);
31+
dsu.unionSets(node1, node3);
32+
33+
DisjointSetUnionBySize.Node<Integer> root1 = dsu.findSet(node1);
34+
DisjointSetUnionBySize.Node<Integer> root2 = dsu.findSet(node2);
35+
DisjointSetUnionBySize.Node<Integer> root3 = dsu.findSet(node3);
36+
DisjointSetUnionBySize.Node<Integer> root4 = dsu.findSet(node4);
37+
38+
assertEquals(root1, root2);
39+
assertEquals(root1, root3);
40+
assertEquals(root1, root4);
41+
assertEquals(4, root1.size);
42+
}
43+
44+
@Test
45+
public void testFindSetOnSingleNode() {
46+
DisjointSetUnionBySize<String> dsu = new DisjointSetUnionBySize<>();
47+
DisjointSetUnionBySize.Node<String> node = dsu.makeSet("A");
48+
assertEquals(node, dsu.findSet(node));
49+
}
50+
51+
@Test
52+
public void testUnionAlreadyConnectedNodes() {
53+
DisjointSetUnionBySize<Integer> dsu = new DisjointSetUnionBySize<>();
54+
DisjointSetUnionBySize.Node<Integer> node1 = dsu.makeSet(1);
55+
DisjointSetUnionBySize.Node<Integer> node2 = dsu.makeSet(2);
56+
DisjointSetUnionBySize.Node<Integer> node3 = dsu.makeSet(3);
57+
58+
dsu.unionSets(node1, node2);
59+
dsu.unionSets(node2, node3);
60+
61+
// Union nodes that are already connected
62+
dsu.unionSets(node1, node3);
63+
64+
// All should have the same root
65+
DisjointSetUnionBySize.Node<Integer> root = dsu.findSet(node1);
66+
assertEquals(root, dsu.findSet(node2));
67+
assertEquals(root, dsu.findSet(node3));
68+
assertEquals(3, root.size);
69+
}
70+
71+
@Test
72+
public void testMultipleMakeSets() {
73+
DisjointSetUnionBySize<Integer> dsu = new DisjointSetUnionBySize<>();
74+
DisjointSetUnionBySize.Node<Integer> node1 = dsu.makeSet(1);
75+
DisjointSetUnionBySize.Node<Integer> node2 = dsu.makeSet(2);
76+
DisjointSetUnionBySize.Node<Integer> node3 = dsu.makeSet(3);
77+
78+
assertNotEquals(node1, node2);
79+
assertNotEquals(node2, node3);
80+
assertNotEquals(node1, node3);
81+
82+
assertEquals(node1, node1.parent);
83+
assertEquals(node2, node2.parent);
84+
assertEquals(node3, node3.parent);
85+
assertEquals(1, node1.size);
86+
assertEquals(1, node2.size);
87+
assertEquals(1, node3.size);
88+
}
89+
90+
@Test
91+
public void testPathCompression() {
92+
DisjointSetUnionBySize<Integer> dsu = new DisjointSetUnionBySize<>();
93+
DisjointSetUnionBySize.Node<Integer> node1 = dsu.makeSet(1);
94+
DisjointSetUnionBySize.Node<Integer> node2 = dsu.makeSet(2);
95+
DisjointSetUnionBySize.Node<Integer> node3 = dsu.makeSet(3);
96+
97+
dsu.unionSets(node1, node2);
98+
dsu.unionSets(node2, node3);
99+
100+
// After findSet, path compression should update parent to root directly
101+
DisjointSetUnionBySize.Node<Integer> root = dsu.findSet(node3);
102+
assertEquals(root, node1);
103+
assertEquals(node1, node3.parent);
104+
assertEquals(3, root.size);
105+
}
106+
107+
@Test
108+
public void testMultipleDisjointSets() {
109+
DisjointSetUnionBySize<Integer> dsu = new DisjointSetUnionBySize<>();
110+
DisjointSetUnionBySize.Node<Integer> node1 = dsu.makeSet(1);
111+
DisjointSetUnionBySize.Node<Integer> node2 = dsu.makeSet(2);
112+
DisjointSetUnionBySize.Node<Integer> node3 = dsu.makeSet(3);
113+
DisjointSetUnionBySize.Node<Integer> node4 = dsu.makeSet(4);
114+
DisjointSetUnionBySize.Node<Integer> node5 = dsu.makeSet(5);
115+
DisjointSetUnionBySize.Node<Integer> node6 = dsu.makeSet(6);
116+
117+
// Create two separate components
118+
dsu.unionSets(node1, node2);
119+
dsu.unionSets(node2, node3);
120+
121+
dsu.unionSets(node4, node5);
122+
dsu.unionSets(node5, node6);
123+
124+
// Verify they are separate
125+
assertEquals(dsu.findSet(node1), dsu.findSet(node2));
126+
assertEquals(dsu.findSet(node2), dsu.findSet(node3));
127+
assertEquals(dsu.findSet(node4), dsu.findSet(node5));
128+
assertEquals(dsu.findSet(node5), dsu.findSet(node6));
129+
130+
assertNotEquals(dsu.findSet(node1), dsu.findSet(node4));
131+
assertNotEquals(dsu.findSet(node3), dsu.findSet(node6));
132+
}
133+
134+
@Test
135+
public void testEmptyValues() {
136+
DisjointSetUnionBySize<String> dsu = new DisjointSetUnionBySize<>();
137+
DisjointSetUnionBySize.Node<String> emptyNode = dsu.makeSet("");
138+
DisjointSetUnionBySize.Node<String> nullNode = dsu.makeSet(null);
139+
140+
assertEquals(emptyNode, dsu.findSet(emptyNode));
141+
assertEquals(nullNode, dsu.findSet(nullNode));
142+
143+
dsu.unionSets(emptyNode, nullNode);
144+
assertEquals(dsu.findSet(emptyNode), dsu.findSet(nullNode));
145+
}
146+
}

0 commit comments

Comments
 (0)