A Bit of History
The Union-Find data structure was introduced by Bernard Galler and Michael Fischer in a short 1964 paper on compiler design, motivated by tracking equivalence classes of program variables. Over the following decade, researchers including M. D. McIlroy, Robert Morris, and independently J. D. Tritter contributed the "union by rank/size" and "path compression" optimizations you'll use here. But the full story of why those optimizations are so effective wasn't nailed down until 1975, when Robert Tarjan (already a familiar name from Parts 6 and 8) proved that a sequence of \(m\) operations on \(n\) elements takes \(O(m \, \alpha(n))\) time, where \(\alpha\) is the inverse Ackermann function — a function that grows so slowly that \(\alpha(n) \leq 4\) for any \(n\) you could ever physically enumerate, even though it is not, strictly speaking, a constant.
The Forest Model
Union–Find stores a partition: every element belongs to exactly one set, sets never overlap, and together they contain every element. Each set is represented as a rooted tree. The root is the set's representative and satisfies parent[root] == root.
Follow parents to a root
The exact root label is arbitrary. Two elements are in the same set precisely when their roots are equal.
Join roots, not arbitrary nodes
Compute both roots first. If they differ, make one root a child of the other and decrease the component count.
union(B,D), A, B, C, and D share representative A.Working Principle
The forest representation needs only one parent entry per element. A root points to itself; every other node points one step closer to its root. The fundamental equivalence is:
$$x \text{ and } y \text{ are connected} \iff \operatorname{find}(x)=\operatorname{find}(y).$$
Calling union(x,y) first replaces both arguments by their roots. If the roots are equal, the operation changes nothing and returns false. Otherwise it links the two trees, updates root-only metadata such as size, decrements the number of sets, and returns true.
| Invariant | Why it matters |
|---|---|
parent[root] == root | Every parent chain has a recognizable stopping point |
| Every node has exactly one parent | The representation is a forest, not an arbitrary graph |
size[root] is valid at roots | Union can attach the smaller set below the larger one |
count decreases only on a successful union | It always equals the number of current sets |
Analogy: Corporate Mergers and Org Charts
Each group is a company's org chart, with one CEO as representative. find(employee) walks up the reporting chain. A merger links one CEO below the other; linking an ordinary employee would give that employee two incompatible roles and violate the forest invariant.
Two Optimizations That Change Everything
1. Union by size or rank
When two roots differ, attach the root of the smaller tree below the root of the larger tree. A node's depth can increase only when its entire set joins a set at least as large, so the size of its containing set at least doubles. That can happen at most $\lfloor\log_2 n\rfloor$ times.
Rank is an alternative upper-bound heuristic for height. When two unequal ranks merge, the larger rank stays unchanged; when equal ranks merge, the winning root's rank increases by one. After path compression, rank is no longer the tree's exact current height—and it does not need to be.
2. Path compression
A find(x) first reaches the root, then redirects every parent pointer on that search path straight to the root. The partition does not change; only its internal representation becomes flatter.
The deliberately tall chain below isolates compression's effect. Union by size would already make such a five-node chain difficult to create through normal unions.
E → D → C → B → A·after find(E):E → ATogether, Not Separately
Union by size prevents bad trees from forming; path compression repairs the paths that queries actually use. Together they yield the $O(\alpha(n))$ amortized bound across a sequence of operations.
The Inverse Ackermann Bound
The Ackermann function grows extraordinarily quickly; its inverse $\alpha(n)$ grows extraordinarily slowly. For every input size that can arise in physical computation, $α(n)$ is a tiny single-digit value. That makes Union–Find effectively constant-time in practice, but the mathematical bound is still not literal $O(1)$.
For $n$ elements and a sequence of $m$ operations using both optimizations:
$$\text{Total time: } O((n+m)\alpha(n)) \qquad \text{Space: } O(n)$$
| Operation | Amortized cost | Important qualifier |
|---|---|---|
| Initialize one element | $O(1)$ | Initializing all $n$ elements costs $O(n)$ |
find | $O(\alpha(n))$ | A single call is not promised worst-case constant time |
union | $O(\alpha(n))$ | It performs two finds plus constant root-link work |
connected | $O(\alpha(n))$ | It compares two representatives |
Amortized Means “Average Over the Sequence”
A particular find may walk several links, but it pays for future calls by flattening them. The theorem bounds the total bill for the whole operation sequence, not every receipt in isolation.
Implementation
The listings use union by size. Only roots have meaningful size values; always call find before reading or updating that metadata.
| Method | Return value | Mutation |
|---|---|---|
find(x) | Representative root | Compresses the visited parent path |
union(x,y) | true only when two sets merge | Links roots, adds sizes, decrements count |
connected(x,y) | Whether representatives match | May compress two paths |
component_size(x) | Size stored at find(x) | May compress one path |
Real-World Applications
Kruskal's MST
Process edges by weight. Add $(u,v)$ only when find(u) != find(v); the union both records the merge and certifies that the edge did not close a cycle.
Component labeling
Union adjacent pixels or cells that belong to the same region. Representatives then provide compact labels for all connected regions.
Percolation
As grid cells open, union each cell with open neighbors. Two virtual boundary nodes make “does top connect to bottom?” a single connectivity query.
Entity merging
Accounts sharing a verified identifier can be unioned transitively. A final scan groups records by representative.
The Monotone Sweet Spot
DSU is ideal when relationships are added and never removed. If edges arrive as a stream, every successful union monotonically coarsens the partition. It answers connectivity, component count, and root-level aggregates without searching the full component on each query.
What DSU Does Not Know
A representative proves that two elements are connected, but the parent forest is not a path in the original graph. Standard DSU cannot return the actual graph path, its length, a shortest path, or the edges responsible for connectivity unless you store additional structures.
Variants & Limitations
| Need | Variant or strategy | Main tradeoff |
|---|---|---|
| Undo recent merges | Rollback DSU | Log parent/size changes and usually omit path compression |
| Offline edge deletions | Process queries backward | A deletion becomes an addition; requires the full query sequence |
| Parity or relative differences | Potential / weighted DSU | Store a value from each node to its parent and update it during compression |
| Arbitrary online insertions and deletions | Fully dynamic connectivity structure | Substantially more complex than DSU |
Standard path-compressed DSU cannot split a component efficiently because its forest remembers the partition, not the history of original edges. Rollback DSU is the important exception to “no undo”: attach by size, record each changed field on a stack, and restore a previous snapshot. Avoid ordinary path compression because one find could rewrite many unlogged pointers.
Pitfalls & Debugging
Six Common Failure Modes
- Comparing raw parents: use
find(x) == find(y); two nodes can share a root without sharing an immediate parent. - Linking non-roots: compute representatives before changing any parent pointer.
- Reading stale size:
size[v]is meaningful only when $v$ is a root. - Decrementing count twice: a redundant union must leave
countunchanged. - Using DSU for directed reachability: ordinary DSU models undirected equivalence, not one-way paths.
- Combining rollback with unlogged compression: compression mutates many pointers and invalidates simple snapshots.
Exercises
- Trace: starting from six singleton sets, run the unions in the first SVG and write the parent and size arrays after every successful merge.
- Redundant edge: call
union(A,D)after A and D are already connected. Explain why the method returns false and the component count does not change. - Height proof: prove that union by size alone limits every node's depth to $\lfloor\log_2 n\rfloor$.
- Percolation: model a 3×3 grid with virtual top and bottom nodes, then find the first opened cell that creates a top-to-bottom connection.
- Rollback challenge: implement
snapshot()androllback(snapshot)using union by size without path compression.
Mental Model to Keep
A representative names a set; it is not an original-graph path. Union by size keeps parent trees shallow, path compression makes observed paths flatter, and every query reduces to comparing roots.