4: Union-Find
Union-Find tracks disjoint sets with two operations:
- Find: Which set contains this element? (Returns the root/representative)
- Union: Merge two sets
With path compression and union by rank, both operations are .
Data Structure
UnifNode:
- key: Key
- parent: Optional[UnifNode] # None if root
- rank: Integer
UnionFind:
- nodes: Dictionary[Key, UnifNode]
- make_set(key) -> UnifNode
- find(key) -> Optional[UnifNode]
- union(x, y) -> UnifNodeEach set is a tree. Elements point to parents. The root is the set’s representative.
Operations
Make Set
make_set(key):
node = UnifNode(key)
node.parent = None
node.rank = 0
nodes.insert(key, node)
return nodeFind (with Path Compression)
find(key):
node = nodes.get(key)
if node.parent is None:
return node
node.parent = find(node.parent.key) # compress path
return node.parentPath compression flattens the tree: after find, all nodes on the path point directly to the root.
Union (by Rank)
union(x, y):
root_x = find(x)
root_y = find(y)
if root_x == root_y:
return root_x
# Attach smaller tree under larger
if root_x.rank < root_y.rank:
root_x.parent = root_y
return root_y
else if root_x.rank > root_y.rank:
root_y.parent = root_x
return root_x
else:
root_y.parent = root_x
root_x.rank += 1
return root_xWhy O(α(n))?
Union by rank: Trees stay balanced. To get rank , need elements. Height is .
Path compression: After any find, the entire path is flattened.
Together: amortized where is inverse Ackermann. for all practical .
Application: Kruskal’s MST
kruskal(graph):
uf = UnionFind()
for v in graph.vertices:
uf.make_set(v)
for edge in sort_by_weight(edges):
if uf.find(edge.u) != uf.find(edge.v):
add edge to MST
uf.union(edge.u, edge.v)Without Union-Find: for connectivity checks. With Union-Find: .
Runtime Summary
| Operation | Time |
|---|---|
| make_set | |
| find | amortized |
| union | amortized |
Limitation: No efficient deletion (no “un-union”).