DSA Notes
Master the Union-Find data structure with path compression and union by rank, achieving nearly O(1) operations for connectivity queries.
The Problem
You need to maintain a collection of disjoint sets and support two operations efficiently:
- Find(x): Which set does element x belong to?
- Union(x, y): Merge the sets containing x and y
This comes up constantly: "Are two nodes connected?" "Merge two groups." "How many connected components exist?" Union-Find answers all of these in nearly O(1) per operation.
The Intuition
Think of a collection of friend groups. Initially, everyone is in their own group. When two people become friends, their entire groups merge. Find asks "who is the leader of x's group?" Union merges two groups by connecting their leaders.
We represent each set as a tree. The root of the tree is the set's representative. Find walks up to the root. Union connects one root to another.
Naive Implementation (Slow)
Problem: Trees can become tall chains → find is O(n) worst case.
Optimization 1: Path Compression
When we call find(x), we walk all the way up to the root. Why not make every node on this path point directly to the root? Next time, find is O(1) for all nodes on that path.
Optimization 2: Union by Rank
When merging two trees, attach the shorter tree under the taller tree. This keeps trees shallow.
- Rank approximates tree height
- Always make the smaller tree a subtree of the larger tree
- This guarantees tree height stays O(log n)
Optimized Implementation
C++ Implementation
Dry Run Example
| Initial | [0][1][2][3][4][5][6] (7 components) |
| union(0, 1): merge {0} and {1} | {0,1} |
| union(2, 3): merge {2} and {3} | {2,3} |
| union(4, 5): merge {4} and {5} | {4,5} |
| find(1) | 0 (root of {0,1}) |
| find(3) | 2 (root of {2,3}) |
| union(1, 3): find(1)=0, find(3)=2, merge | {0,1,2,3} |
| union(5, 6): merge {4,5} and {6} | {4,5,6} |
| Components: {0,1,2,3}, {4,5,6} | 2 components |
Time Complexity
With both path compression AND union by rank:
- Find: O(α(n)) amortized — where α is the inverse Ackermann function
- Union: O(α(n)) amortized
- α(n) ≤ 4 for any practical input (n < 10^80)
- Effectively O(1) per operation!
Without optimizations: O(n) worst case per find.
Common Applications
1. Connected Components
def count_components(n, edges):
uf = UnionFind(n)
for u, v in edges:
uf.union(u, v)
return uf.num_components()2. Cycle Detection in Undirected Graph
def has_cycle(n, edges):
uf = UnionFind(n)
for u, v in edges:
if not uf.union(u, v): # Already connected = cycle!
return True
return False3. Kruskal's MST Algorithm
Union-Find is the backbone of Kruskal's — it checks whether adding an edge would create a cycle.
4. Network Connectivity
"Is server A reachable from server B?" — maintain Union-Find as connections are added.
Advanced: Union-Find with Size
Sometimes you need the size of each component:
Key Takeaways
- Union-Find is the go-to for dynamic connectivity problems
- Always use both optimizations (path compression + union by rank/size)
- It is NOT good for disconnection operations (splitting sets is hard)
- Operations are amortized O(α(n)) ≈ O(1) in practice
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Union-Find (Disjoint Set Union).
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Data Structures & Algorithms topic.
Search Terms
data-structures-algorithms, data structures & algorithms, data, structures, algorithms, graphs, union, find
Related Data Structures & Algorithms Topics