3.5.15 · D1Graphs

Foundations — Disjoint Set Union (Union-Find) — path compression + union by rank → α(n)

2,791 words13 min readBack to topic

Before you can read the parent note, you need to see every piece it silently assumes. We build them one at a time, each on top of the last. Nothing is used before it is drawn.


1. A node, and an arrow that points somewhere

The most basic object here is a node — think of it as a labelled dot. We have of them, numbered .

Why an array? Because a node is just a number , so parent is a lookup table: "give me a node number, I give you where it points." That single array is the entire data structure.

Look at the picture: every dot has exactly one outgoing arrow (that is the rule — one parent each). The dot whose arrow curls back on itself is the boss.


2. What a "set" looks like: a rooted tree

If every node has exactly one parent arrow, and you keep following arrows, you always end at a root (the arrows can't loop anywhere else — only the root loops on itself). That shape has a name.


3. Height, and why we fear it

Walk the figure. On the left is a tall stringy tree: node 3 points to 2, 2 points to 1, 1 points to root 0. To answer "who is 3's boss?" you must follow 3 arrows — the height is . On the right is a bushy tree: four nodes all point directly at root R, so any find is 1 arrow — height . Same number of nodes, wildly different cost. This is the villain of the whole story: a straight-line tree (height ) makes a single find cost hops. The two optimizations in the parent note exist entirely to keep this height tiny — short + bushy = good, tall + stringy = bad.


4. find(x) — climbing to the boss, and path compression

Now we can read the first operation. find(x) means: start at , follow parent arrows until you hit a root, return that root.

Why do we need it at all? Because "are and in the same group?" becomes simply find(x) == find(y) — compare the two bosses. Two people are teammates if and only if they salute the same captain.

Look at the figure: before, 5→4→0 is a chain; after a single find(5), both 5 and 4 point directly at root 0. We paid a little extra now to make every future find cheap — that is exactly why the cost is called amortized (§7).


5. union(x, y) — merging, by rank, with a same-set guard

The picture shows the entire operation: we do NOT touch any leaf; we only redirect one boss to bow to another boss. This is why a tree beats a list — a merge is one pointer change. This same "join two groups" motion is what powers Kruskal's Minimum Spanning Tree and Connected Components.

But which root bows to which? And what if and are already in the same group? Here is the full procedure.


6. rank[x] — a cheap stand-in for height

So rank is not a measurement — it is a tie-breaking hint for union that is always at least the true height. Never recompute it downward.


7. Big-O and "amortized" — the language of cost

The parent note talks about , , . These describe how the work grows as grows.


8. The Ackermann function and its inverse

Why does this monster show up? Because the exact cost of path-compression-plus-rank, when analysed carefully, turns out to match the inverse of Ackermann's growth. In practice, treat as "a constant " — that is the whole point.


Putting the pieces together

node + parent array

rooted tree = one set

forest = many trees

root = representative

height = longest chain

find x climbs to root

path compression flattens

union x y joins two roots

rank = height upper bound

Big-O cost of a chain

amortized cost over sequence

alpha n inverse Ackermann

DSU with path compression + union by rank

Read it bottom-up to the box DSU: nodes make trees, trees form a forest with one root each, roots enable find/union, height forces us to invent rank, cost language plus amortized analysis produce the guarantee — and all of it feeds the parent topic. Related uses branch off toward Graph Cycle Detection and Connected Components.


Equipment checklist

Test yourself — cover the right side and answer out loud.

How do you initialize a fresh DSU on nodes ?
For every : parent[x] = x and rank[x] = 0 — each node is its own root, height .
What does parent[x] == x mean?
is a root — the representative/boss of its set.
What is the single array that stores the entire DSU?
parent[], where parent[x] is the one arrow leaving node .
What is a forest, in this context?
A collection of separate rooted trees — one tree per disjoint set, no root pointing at another.
Why store each set as a tree, not a flat list?
A merge is one pointer change and "same set?" is one climb to the root, instead of scanning lists.
What is a tree's height, in plain words?
The longest chain of parent-arrows from any node up to the root — the worst-case number of hops.
What does find(x) return and how does it know it's done?
The root of 's tree; it stops when parent[x] == x.
What does path compression do during a find?
Repoints every node on the climbed path directly at the root, so future finds are one hop.
In union, how do the ranks decide who bows to whom?
The larger-rank root wins; the smaller bows to it. On a tie, either wins and its rank goes up by 1.
Why must union early-return when find(x) == find(y)?
They're already one set; skipping the guard risks a self-loop write or a bogus rank increment that corrupts the structure.
Why is rank always ≥ the true height?
union bumps rank exactly when height rises; path compression only lowers height and never touches rank — so rank can only over-estimate.
What does "amortized " mean?
Averaged over a whole sequence of operations, each costs about — effectively constant.
What do the two arguments of represent?
picks how explosive the operation is (add, multiply, power, …); is how many times to apply it.
Why does stay in practice?
It inverts Ackermann's insanely fast growth, so it grows insanely slowly.