1+ # Disjoint Set Union (Union-Find)
2+ #
3+ # A data structure that keeps track of elements partitioned into disjoint sets.
4+ # Supports union and find operations efficiently using path compression and union by rank.
5+ #
6+ # Time Complexity: Nearly O(1) per operation (amortized with path compression)
7+ # Space Complexity: O(N)
8+ #
9+ # Applications:
10+ # - Detecting cycles in undirected graphs
11+ # - Kruskal's MST algorithm
12+ # - Network connectivity
13+ # - Social network grouping
14+ # - Image segmentation
15+ # - Connected components in graphs
16+
17+ make_set <- function (n ) {
18+ # ' Initialize disjoint set with n elements
19+ # ' @param n: Number of elements (0 to n-1)
20+ # ' @return: List containing parent array and rank array
21+
22+ # Each element is initially its own parent
23+ parent <- 0 : (n - 1 )
24+ # Initial rank (tree height) is 0 for all elements
25+ rank <- rep(0 , n )
26+
27+ list (parent = parent , rank = rank )
28+ }
29+
30+ find_set <- function (ds , v ) {
31+ # ' Find the representative (root) of the set containing v
32+ # ' Uses path compression for efficiency
33+ # ' @param ds: Disjoint set data structure
34+ # ' @param v: Element to find
35+ # ' @return: Representative of v's set
36+
37+ if (ds $ parent [v + 1 ] != v ) {
38+ # Path compression: Make all nodes on path point to root
39+ ds $ parent [v + 1 ] <- find_set(ds , ds $ parent [v + 1 ])
40+ }
41+ ds $ parent [v + 1 ]
42+ }
43+
44+ union_sets <- function (ds , a , b ) {
45+ # ' Union two sets by rank
46+ # ' @param ds: Disjoint set data structure
47+ # ' @param a: First element
48+ # ' @param b: Second element
49+ # ' @return: Updated disjoint set structure
50+
51+ a_root <- find_set(ds , a )
52+ b_root <- find_set(ds , b )
53+
54+ if (a_root != b_root ) {
55+ # Union by rank: Attach smaller rank tree under root of higher rank tree
56+ if (ds $ rank [a_root + 1 ] < ds $ rank [b_root + 1 ]) {
57+ # Swap to ensure a_root has higher rank
58+ temp <- a_root
59+ a_root <- b_root
60+ b_root <- temp
61+ }
62+
63+ ds $ parent [b_root + 1 ] <- a_root
64+
65+ # If ranks are equal, increment the rank of the root
66+ if (ds $ rank [a_root + 1 ] == ds $ rank [b_root + 1 ]) {
67+ ds $ rank [a_root + 1 ] <- ds $ rank [a_root + 1 ] + 1
68+ }
69+ }
70+
71+ return (ds )
72+ }
73+
74+ # Example Usage
75+ # Test case 1: Basic operations
76+ cat(" === Test Case 1: Basic Union-Find Operations ===\n " )
77+ ds1 <- make_set(5 ) # Create sets for elements 0-4
78+ cat(" Initial parents:" , paste(ds1 $ parent , collapse = " " ), " \n " )
79+
80+ ds1 <- union_sets(ds1 , 0 , 1 ) # Union first two elements
81+ ds1 <- union_sets(ds1 , 2 , 3 ) # Union next two elements
82+ cat(" After unions:" , paste(ds1 $ parent , collapse = " " ), " \n " )
83+
84+ cat(" Find(1):" , find_set(ds1 , 1 ), " \n " )
85+ cat(" Find(2):" , find_set(ds1 , 2 ), " \n " )
86+
87+ # Test case 2: Path compression
88+ cat(" \n === Test Case 2: Path Compression ===\n " )
89+ ds2 <- make_set(6 )
90+ ds2 <- union_sets(ds2 , 0 , 1 )
91+ ds2 <- union_sets(ds2 , 1 , 2 )
92+ ds2 <- union_sets(ds2 , 2 , 3 )
93+ cat(" Parents before find:" , paste(ds2 $ parent , collapse = " " ), " \n " )
94+ find_set(ds2 , 3 ) # This should compress the path
95+ cat(" Parents after find:" , paste(ds2 $ parent , collapse = " " ), " \n " )
96+
97+ # Test case 3: Connected components
98+ cat(" \n === Test Case 3: Finding Connected Components ===\n " )
99+ ds3 <- make_set(7 )
100+ edges <- list (c(0 ,1 ), c(1 ,2 ), c(3 ,4 ), c(5 ,6 ))
101+ for (edge in edges ) {
102+ ds3 <- union_sets(ds3 , edge [1 ], edge [2 ])
103+ }
104+
105+ # Print connected components
106+ components <- list ()
107+ for (i in 0 : 6 ) {
108+ root <- find_set(ds3 , i )
109+ if (is.null(components [[as.character(root )]])) {
110+ components [[as.character(root )]] <- c()
111+ }
112+ components [[as.character(root )]] <- c(components [[as.character(root )]], i )
113+ }
114+
115+ cat(" Connected Components:\n " )
116+ for (comp in components ) {
117+ cat(" Group:" , paste(comp , collapse = " " ), " \n " )
118+ }
0 commit comments