Union-Find¶
Edited by Charles
Easy · Python · Graph
Problem
This is a data-structure study note rather than a single programming problem.
Maintain a collection of disjoint sets with these operations:
find(x): return the representative of the set containingx.union(u, v): merge the sets containinguandv.is_same(u, v): determine whetheruandvbelong to the same set.
Initially, each of the n elements belongs to its own set. The note covers
path compression and union by rank, with examples of connectivity queries.
Explanation, examples & other approaches
Core idea
Union Find, also called Disjoint Set Union (DSU), is a data structure used to manage multiple disjoint sets.
Original study notes follow, including intermediate snippets and earlier attempts.
Idea¶
Union Find, also called Disjoint Set Union (DSU), is a data structure used to manage multiple disjoint sets.
It is especially useful for problems involving:
The key operations are:
find(x) -> find the root of x
union(u, v) -> merge the sets containing u and v
is_same(u,v)-> check whether u and v belong to the same set
Basic Representation¶
Instead of explicitly storing every set separately, we use a one-dimensional array:
where:
means that y is the parent of x.
For example:
can be represented as:
Since:
C is the root of the set.
Therefore:
Since they have the same root, they belong to the same set.
Initialization¶
Initially, every element belongs to its own set.
Therefore:
For example:
In Python:
or simply:
Find¶
The find operation returns the root of an element.
Without optimization:
For example:
Then:
works like:
So the root of 1 is 3.
Path Compression¶
The basic find operation may create very deep trees.
For example:
Finding the root of 1 requires following many parent links.
Path compression makes every visited node point directly to the root.
Before:
After:
Python implementation:
The important line is:
It both finds the root and updates the parent pointer.
Union¶
To merge two elements u and v, we should not directly connect:
because u and v may not be roots.
Instead, first find their roots:
If:
they already belong to the same set.
Otherwise, connect one root to the other.
Basic version:
def union(u, v):
root_u = find(u)
root_v = find(v)
if root_u == root_v:
return
parent[root_v] = root_u
Check Whether Two Elements Are Connected¶
Two elements belong to the same set if they have the same root.
For example:
Then:
so:
Path compression optimizes find.
Another optimization is Union by Rank.
The idea is:
Always attach the shorter tree under the taller tree.
This prevents the tree from becoming unnecessarily deep.
We maintain another array:
where rank[x] represents an approximate height of the tree rooted at x.
Initialization:
Union by Rank Logic¶
Suppose:
Then attach root_u under root_v:
If:
attach root_v under root_u.
If the ranks are equal, either root can become the new root.
For example:
Python Implementation¶
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [1] * n
def find(self, x):
if x != self.parent[x]:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, u, v):
root_u = self.find(u)
root_v = self.find(v)
if root_u == root_v:
return False
if self.rank[root_u] < self.rank[root_v]:
self.parent[root_u] = root_v
elif self.rank[root_u] > self.rank[root_v]:
self.parent[root_v] = root_u
else:
self.parent[root_v] = root_u
self.rank[root_u] += 1
return True
def is_same(self, u, v):
return self.find(u) == self.find(v)
Example¶
Suppose:
Initially:
Each element belongs to its own set.
Union 1 and 2¶
Possible structure:
Now:
returns:
Union 2 and 3¶
Since:
we actually merge:
Possible result:
Now:
Therefore:
returns:
Why Do We Union the Roots?¶
Suppose:
and:
If we want to merge 1 and 4, we should first find:
Then connect:
or:
This merges the entire two sets.
If we only write:
we may not correctly connect the two tree structures.
Therefore:
Always find the roots before performing union.
Why Does Union Find Work?¶
Each connected component is represented by one tree.
All elements in the same set eventually point to the same root.
Therefore:
Different roots mean:
The root acts like the ID of the connected component.
Time Complexity¶
With both:
the amortized time complexity of find and union is approximately:
where α(n) is the inverse Ackermann function.
For all practical input sizes:
So Union Find operations are effectively very close to:
Summary¶
Initialization¶
Each element starts as its own root.
Find¶
Use path compression to make future searches faster.
Union¶
1. Find the roots of u and v.
2. If the roots are equal, they are already connected.
3. Otherwise, merge the two roots.
4. Attach the lower-rank tree under the higher-rank tree.
Check Connectivity¶
means that u and v belong to the same set.
Common Mistakes¶
- Do not directly connect
uandvwithout finding their roots first. - Do not forget initialization:
Every element must initially be its own root.
- Path compression happens inside
find:
- Union by rank should compare the roots' ranks, not the original nodes' ranks.
- If two nodes already have the same root, do not merge them again.
- Union Find is mainly designed for undirected connectivity problems. It is not generally a replacement for DFS/BFS in arbitrary directed reachability problems.
Study record
| Field | Value |
|---|---|
| Attempts | 1 |
| Mastery | ❚ |
| The Latest Attempt | August 23, 2026 |
Python solution¶
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [1] * n
def find(self, x):
if x != self.parent[x]:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
root_x = self.find(x)
root_y = self.find(y)
if root_x == root_y:
return False
if self.rank[root_x] < self.rank[root_y]:
root_x, root_y = root_y, root_x
self.parent[root_y] = root_x
if self.rank[root_x] == self.rank[root_y]:
self.rank[root_x] += 1
return True
def is_same(self, x, y):
return self.find(x) == self.find(y)