Skip to content

Commit 35ec9e9

Browse files
author
Ranjita1708
committed
feat: add Disjoint Set Union with path compression and union by rank
1 parent 2c15b8c commit 35ec9e9

File tree

1 file changed

+176
-0
lines changed

1 file changed

+176
-0
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
"""
2+
Disjoint Set Union (Union-Find) data structure with path compression
3+
and union by rank optimizations.
4+
5+
Reference: https://en.wikipedia.org/wiki/Disjoint-set_data_structure
6+
7+
Time Complexity:
8+
- find: O(alpha(n)) amortized, where alpha is the inverse Ackermann function
9+
- union: O(alpha(n)) amortized
10+
- connected: O(alpha(n)) amortized
11+
12+
Space Complexity: O(n)
13+
"""
14+
15+
16+
class DisjointSetUnion:
17+
"""
18+
A Disjoint Set Union (Union-Find) data structure supporting efficient
19+
union and find operations with path compression and union by rank.
20+
21+
>>> dsu = DisjointSetUnion(5)
22+
>>> dsu.find(0)
23+
0
24+
>>> dsu.union(0, 1)
25+
>>> dsu.connected(0, 1)
26+
True
27+
>>> dsu.connected(0, 2)
28+
False
29+
>>> dsu.union(1, 2)
30+
>>> dsu.connected(0, 2)
31+
True
32+
"""
33+
34+
def __init__(self, size: int) -> None:
35+
"""
36+
Initialize a Disjoint Set Union with `size` elements (0 to size-1).
37+
38+
Args:
39+
size: The number of elements in the disjoint set.
40+
41+
Raises:
42+
ValueError: If size is not a positive integer.
43+
44+
>>> dsu = DisjointSetUnion(5)
45+
>>> len(dsu.parent)
46+
5
47+
>>> dsu = DisjointSetUnion(0)
48+
Traceback (most recent call last):
49+
...
50+
ValueError: size must be a positive integer
51+
>>> dsu = DisjointSetUnion(-1)
52+
Traceback (most recent call last):
53+
...
54+
ValueError: size must be a positive integer
55+
"""
56+
if size <= 0:
57+
raise ValueError("size must be a positive integer")
58+
self.size = size
59+
self.parent = list(range(size))
60+
self.rank = [0] * size
61+
62+
def find(self, element: int) -> int:
63+
"""
64+
Find the representative (root) of the set containing `element`.
65+
Uses path compression for optimization.
66+
67+
Args:
68+
element: The element to find the representative of.
69+
70+
Returns:
71+
The representative of the set containing element.
72+
73+
Raises:
74+
IndexError: If element is out of bounds.
75+
76+
>>> dsu = DisjointSetUnion(5)
77+
>>> dsu.find(0)
78+
0
79+
>>> dsu.union(0, 1)
80+
>>> dsu.find(1) == dsu.find(0)
81+
True
82+
>>> dsu.find(5)
83+
Traceback (most recent call last):
84+
...
85+
IndexError: element 5 is out of bounds for size 5
86+
>>> dsu.find(-1)
87+
Traceback (most recent call last):
88+
...
89+
IndexError: element -1 is out of bounds for size 5
90+
"""
91+
if element < 0 or element >= self.size:
92+
msg = f"element {element} is out of bounds for size {self.size}"
93+
raise IndexError(msg)
94+
95+
if self.parent[element] != element:
96+
self.parent[element] = self.find(self.parent[element])
97+
return self.parent[element]
98+
99+
def union(self, element1: int, element2: int) -> None:
100+
"""
101+
Merge the sets containing `element1` and `element2`.
102+
Uses union by rank for optimization.
103+
104+
Args:
105+
element1: An element in the first set.
106+
element2: An element in the second set.
107+
108+
Raises:
109+
IndexError: If either element is out of bounds.
110+
111+
>>> dsu = DisjointSetUnion(5)
112+
>>> dsu.union(0, 1)
113+
>>> dsu.connected(0, 1)
114+
True
115+
>>> dsu.union(2, 3)
116+
>>> dsu.union(0, 3)
117+
>>> dsu.connected(1, 2)
118+
True
119+
>>> dsu.union(4, 4) # Self-union should not corrupt the structure
120+
>>> dsu.find(4)
121+
4
122+
>>> dsu.union(5, 0)
123+
Traceback (most recent call last):
124+
...
125+
IndexError: element 5 is out of bounds for size 5
126+
"""
127+
root1 = self.find(element1)
128+
root2 = self.find(element2)
129+
130+
if root1 == root2:
131+
return
132+
133+
if self.rank[root1] < self.rank[root2]:
134+
self.parent[root1] = root2
135+
elif self.rank[root1] > self.rank[root2]:
136+
self.parent[root2] = root1
137+
else:
138+
self.parent[root2] = root1
139+
self.rank[root1] += 1
140+
141+
def connected(self, element1: int, element2: int) -> bool:
142+
"""
143+
Check if `element1` and `element2` belong to the same set.
144+
145+
Args:
146+
element1: The first element.
147+
element2: The second element.
148+
149+
Returns:
150+
True if both elements are in the same set, False otherwise.
151+
152+
Raises:
153+
IndexError: If either element is out of bounds.
154+
155+
>>> dsu = DisjointSetUnion(5)
156+
>>> dsu.connected(0, 1)
157+
False
158+
>>> dsu.union(0, 1)
159+
>>> dsu.connected(0, 1)
160+
True
161+
>>> dsu.connected(1, 0)
162+
True
163+
>>> dsu.connected(0, 0)
164+
True
165+
>>> dsu.connected(0, 5)
166+
Traceback (most recent call last):
167+
...
168+
IndexError: element 5 is out of bounds for size 5
169+
"""
170+
return self.find(element1) == self.find(element2)
171+
172+
173+
if __name__ == "__main__":
174+
import doctest
175+
176+
doctest.testmod()

0 commit comments

Comments
 (0)