Disjoint sets & union-find
◈ 5 cardsMAKE-SET / UNION / FIND-SET; union by rank + path compression → O(m·α(n)); the engine of Kruskal.
The connectivity structure
A disjoint-set (union-find) structure maintains a collection of non-overlapping sets, each named by a representative member, and answers "are these two elements in the same set?" extremely fast. Three operations:
MAKE-SET(x)— create a singleton . .UNION(x, y)— merge the sets containing and into one (destroying the originals).FIND-SET(x)— return the representative of 's set.
The defining property: FIND-SET(u) == FIND-SET(v) iff and are in the same set. With MAKE-SET calls there can be at most UNIONs (each merge drops the set count by one). This is the workhorse behind connected components of a graph and, most importantly for this course, Kruskal's MST algorithm (Module 7): process edges cheapest-first, and add edge only when FIND-SET(u) != FIND-SET(v) (i.e. it joins two different components), then UNION them.
Disjoint-set forests and two heuristics
The efficient representation is a forest: each set is a rooted tree where every node points only to its parent, and the root (its own parent) is the representative. FIND-SET walks parent pointers up to the root; UNION links one root under another. Naively this can build a height- chain and make FIND-SET . Two heuristics fix it:
- Union by rank — each node keeps a
rank(an upper bound on its height). In a union, the root of smaller rank points to the root of larger rank; on a tie, pick either and bump its rank. This keeps trees shallow, analogous to the weighted-union heuristic for lists. - Path compression — during
FIND-SET, make every node on the path point directly to the root. Since you walk to the root anyway, flattening is nearly free, and it makes future finds on those nodes . (Path compression never changes ranks — rank stays an upper bound on height, not the exact height.)
The running time: essentially linear
The bounds stack up:
- Naive forest: per
FIND-SET. - Union by rank alone: for operations.
- Union by rank + path compression: — where is the inverse Ackermann function.
grows so absurdly slowly that for any up to the number of atoms in the observable universe (). So is "essentially linear" — effectively amortized per operation — even though, technically, it is superlinear (Tarjan proved is also a lower bound, so you cannot do strictly better).
Worked example — six elements, four unions
Start with MAKE-SET on — six singletons, six components. Now UNION(0,1), UNION(2,3), UNION(1,3), UNION(4,5):
UNION(0,1)merges and → .UNION(2,3)→ .UNION(1,3)merges the and components (because is now in the same tree as , and with ) → .UNION(4,5)→ .
Now FIND-SET(0) == FIND-SET(3) is True (both in ), but FIND-SET(0) == FIND-SET(5) is False ( vs ). Two components remain. Each FIND-SET after path compression points its path's nodes straight at the root, so repeated connectivity queries are effectively — this is exactly the "are these two vertices already connected?" test Kruskal runs on every edge.