mirror of
https://github.com/TheAlgorithms/Java.git
synced 2026-02-04 04:23:32 +08:00
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>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
package com.thealgorithms.datastructures.disjointsetunion;
|
||||
|
||||
/**
|
||||
* Disjoint Set Union (DSU) with Union by Size.
|
||||
* This data structure tracks a set of elements partitioned into disjoint (non-overlapping) subsets.
|
||||
* It supports two primary operations efficiently:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Find: Determine which subset a particular element belongs to.</li>
|
||||
* <li>Union: Merge two subsets into a single subset using union by size.</li>
|
||||
* </ul>
|
||||
*
|
||||
* Union by size always attaches the smaller tree under the root of the larger tree.
|
||||
* This helps keep the tree shallow, improving the efficiency of find operations.
|
||||
*
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure">Disjoint Set Union (Wikipedia)</a>
|
||||
*/
|
||||
public class DisjointSetUnionBySize<T> {
|
||||
/**
|
||||
* Node class for DSU by size.
|
||||
* Each node keeps track of its parent and the size of the set it represents.
|
||||
*/
|
||||
public static class Node<T> {
|
||||
public T value;
|
||||
public Node<T> parent;
|
||||
public int size; // size of the set
|
||||
|
||||
public Node(T value) {
|
||||
this.value = value;
|
||||
this.parent = this;
|
||||
this.size = 1; // initially, the set size is 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new disjoint set containing the single specified element.
|
||||
* @param value the element to be placed in a new singleton set
|
||||
* @return a node representing the new set
|
||||
*/
|
||||
public Node<T> makeSet(final T value) {
|
||||
return new Node<>(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the representative (root) of the set containing the given node.
|
||||
* This method applies path compression to flatten the tree structure for future efficiency.
|
||||
* @param node the node whose set representative is to be found
|
||||
* @return the representative (root) node of the set
|
||||
*/
|
||||
public Node<T> findSet(Node<T> node) {
|
||||
if (node != node.parent) {
|
||||
node.parent = findSet(node.parent); // path compression
|
||||
}
|
||||
return node.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the sets containing the two given nodes using union by size.
|
||||
* The root of the smaller set is attached to the root of the larger set.
|
||||
* @param x a node in the first set
|
||||
* @param y a node in the second set
|
||||
*/
|
||||
public void unionSets(Node<T> x, Node<T> y) {
|
||||
Node<T> rootX = findSet(x);
|
||||
Node<T> rootY = findSet(y);
|
||||
|
||||
if (rootX == rootY) {
|
||||
return; // They are already in the same set
|
||||
}
|
||||
// Union by size: attach smaller tree under the larger one
|
||||
if (rootX.size < rootY.size) {
|
||||
rootX.parent = rootY;
|
||||
rootY.size += rootX.size; // update size
|
||||
} else {
|
||||
rootY.parent = rootX;
|
||||
rootX.size += rootY.size; // update size
|
||||
}
|
||||
}
|
||||
}
|
||||
// This implementation uses union by size instead of union by rank.
|
||||
// The size field tracks the number of elements in each set.
|
||||
// When two sets are merged, the smaller set is always attached to the larger set's root.
|
||||
// This helps keep the tree shallow and improves the efficiency of find operations.
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.thealgorithms.datastructures.disjointsetunion;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class DisjointSetUnionBySizeTest {
|
||||
|
||||
@Test
|
||||
public void testMakeSet() {
|
||||
DisjointSetUnionBySize<Integer> dsu = new DisjointSetUnionBySize<>();
|
||||
DisjointSetUnionBySize.Node<Integer> node = dsu.makeSet(1);
|
||||
assertNotNull(node);
|
||||
assertEquals(node, node.parent);
|
||||
assertEquals(1, node.size);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnionFindSet() {
|
||||
DisjointSetUnionBySize<Integer> dsu = new DisjointSetUnionBySize<>();
|
||||
DisjointSetUnionBySize.Node<Integer> node1 = dsu.makeSet(1);
|
||||
DisjointSetUnionBySize.Node<Integer> node2 = dsu.makeSet(2);
|
||||
DisjointSetUnionBySize.Node<Integer> node3 = dsu.makeSet(3);
|
||||
DisjointSetUnionBySize.Node<Integer> node4 = dsu.makeSet(4);
|
||||
|
||||
dsu.unionSets(node1, node2);
|
||||
dsu.unionSets(node3, node2);
|
||||
dsu.unionSets(node3, node4);
|
||||
dsu.unionSets(node1, node3);
|
||||
|
||||
DisjointSetUnionBySize.Node<Integer> root1 = dsu.findSet(node1);
|
||||
DisjointSetUnionBySize.Node<Integer> root2 = dsu.findSet(node2);
|
||||
DisjointSetUnionBySize.Node<Integer> root3 = dsu.findSet(node3);
|
||||
DisjointSetUnionBySize.Node<Integer> root4 = dsu.findSet(node4);
|
||||
|
||||
assertEquals(root1, root2);
|
||||
assertEquals(root1, root3);
|
||||
assertEquals(root1, root4);
|
||||
assertEquals(4, root1.size);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindSetOnSingleNode() {
|
||||
DisjointSetUnionBySize<String> dsu = new DisjointSetUnionBySize<>();
|
||||
DisjointSetUnionBySize.Node<String> node = dsu.makeSet("A");
|
||||
assertEquals(node, dsu.findSet(node));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnionAlreadyConnectedNodes() {
|
||||
DisjointSetUnionBySize<Integer> dsu = new DisjointSetUnionBySize<>();
|
||||
DisjointSetUnionBySize.Node<Integer> node1 = dsu.makeSet(1);
|
||||
DisjointSetUnionBySize.Node<Integer> node2 = dsu.makeSet(2);
|
||||
DisjointSetUnionBySize.Node<Integer> node3 = dsu.makeSet(3);
|
||||
|
||||
dsu.unionSets(node1, node2);
|
||||
dsu.unionSets(node2, node3);
|
||||
|
||||
// Union nodes that are already connected
|
||||
dsu.unionSets(node1, node3);
|
||||
|
||||
// All should have the same root
|
||||
DisjointSetUnionBySize.Node<Integer> root = dsu.findSet(node1);
|
||||
assertEquals(root, dsu.findSet(node2));
|
||||
assertEquals(root, dsu.findSet(node3));
|
||||
assertEquals(3, root.size);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleMakeSets() {
|
||||
DisjointSetUnionBySize<Integer> dsu = new DisjointSetUnionBySize<>();
|
||||
DisjointSetUnionBySize.Node<Integer> node1 = dsu.makeSet(1);
|
||||
DisjointSetUnionBySize.Node<Integer> node2 = dsu.makeSet(2);
|
||||
DisjointSetUnionBySize.Node<Integer> node3 = dsu.makeSet(3);
|
||||
|
||||
assertNotEquals(node1, node2);
|
||||
assertNotEquals(node2, node3);
|
||||
assertNotEquals(node1, node3);
|
||||
|
||||
assertEquals(node1, node1.parent);
|
||||
assertEquals(node2, node2.parent);
|
||||
assertEquals(node3, node3.parent);
|
||||
assertEquals(1, node1.size);
|
||||
assertEquals(1, node2.size);
|
||||
assertEquals(1, node3.size);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathCompression() {
|
||||
DisjointSetUnionBySize<Integer> dsu = new DisjointSetUnionBySize<>();
|
||||
DisjointSetUnionBySize.Node<Integer> node1 = dsu.makeSet(1);
|
||||
DisjointSetUnionBySize.Node<Integer> node2 = dsu.makeSet(2);
|
||||
DisjointSetUnionBySize.Node<Integer> node3 = dsu.makeSet(3);
|
||||
|
||||
dsu.unionSets(node1, node2);
|
||||
dsu.unionSets(node2, node3);
|
||||
|
||||
// After findSet, path compression should update parent to root directly
|
||||
DisjointSetUnionBySize.Node<Integer> root = dsu.findSet(node3);
|
||||
assertEquals(root, node1);
|
||||
assertEquals(node1, node3.parent);
|
||||
assertEquals(3, root.size);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleDisjointSets() {
|
||||
DisjointSetUnionBySize<Integer> dsu = new DisjointSetUnionBySize<>();
|
||||
DisjointSetUnionBySize.Node<Integer> node1 = dsu.makeSet(1);
|
||||
DisjointSetUnionBySize.Node<Integer> node2 = dsu.makeSet(2);
|
||||
DisjointSetUnionBySize.Node<Integer> node3 = dsu.makeSet(3);
|
||||
DisjointSetUnionBySize.Node<Integer> node4 = dsu.makeSet(4);
|
||||
DisjointSetUnionBySize.Node<Integer> node5 = dsu.makeSet(5);
|
||||
DisjointSetUnionBySize.Node<Integer> node6 = dsu.makeSet(6);
|
||||
|
||||
// Create two separate components
|
||||
dsu.unionSets(node1, node2);
|
||||
dsu.unionSets(node2, node3);
|
||||
|
||||
dsu.unionSets(node4, node5);
|
||||
dsu.unionSets(node5, node6);
|
||||
|
||||
// Verify they are separate
|
||||
assertEquals(dsu.findSet(node1), dsu.findSet(node2));
|
||||
assertEquals(dsu.findSet(node2), dsu.findSet(node3));
|
||||
assertEquals(dsu.findSet(node4), dsu.findSet(node5));
|
||||
assertEquals(dsu.findSet(node5), dsu.findSet(node6));
|
||||
|
||||
assertNotEquals(dsu.findSet(node1), dsu.findSet(node4));
|
||||
assertNotEquals(dsu.findSet(node3), dsu.findSet(node6));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyValues() {
|
||||
DisjointSetUnionBySize<String> dsu = new DisjointSetUnionBySize<>();
|
||||
DisjointSetUnionBySize.Node<String> emptyNode = dsu.makeSet("");
|
||||
DisjointSetUnionBySize.Node<String> nullNode = dsu.makeSet(null);
|
||||
|
||||
assertEquals(emptyNode, dsu.findSet(emptyNode));
|
||||
assertEquals(nullNode, dsu.findSet(nullNode));
|
||||
|
||||
dsu.unionSets(emptyNode, nullNode);
|
||||
assertEquals(dsu.findSet(emptyNode), dsu.findSet(nullNode));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user