Home/Graphs/Union-Find
Search algorithmsCTRL K

Union-Find

· disjoint sets · components as edges arrive
/graphs/union-find
COMPONENTS
10
found so far
UNIONS
0
sets merged
FINDS
0
representative lookups
EDGES
0/ 11
arrived so far
LARGEST
1
nodes in the biggest component
representativepoints at anotheredge examinedalready connected (dashed)
10 nodes · 11 edges · seed 7 · 24 steps
step 0 / 23
Nodes10
union_find.ts
1
function connectedComponents(graph: Graph) {
2
  const parent = new Map(graph.nodes.map((node) => [node, node]));
3
  const find = (node: Node): Node => (parent.get(node) === node ? node : (parent.set(node, find(parent.get(node)!)), parent.get(node)!));
4
  for (const edge of graph.edges) {
5
    const rootA = find(edge.from), rootB = find(edge.to);
6
    if (rootA === rootB) continue;
7
    parent.set(rootA, rootB);
8
  }
9
  return new Set(graph.nodes.map(find)).size;
10
}
CURRENT STEP
line 2

Every node starts as its own set; the label is its representative. Edges arrive in a shuffled order, as if streamed.

// how it works

Understanding Union-Find

union-find · two pointers chase and a whole component merges
01

The idea

Union-find keeps a forest: every node points at a parent, and the root of its tree is the representative of its set. Find follows the pointers to the root; union makes one root point at the other. Two nodes are connected exactly when their finds return the same root.

Two tricks make it almost free. Path compression makes every node visited by a find point straight at the root afterwards, and union by rank keeps trees shallow. With both, a sequence of m operations costs O(m · α(n)), where α is the inverse Ackermann function, below 5 for any input that fits in the universe.

02

The four stages

1
initevery node is its own set
2
findfollow parents to the root, compressing
3
comparesame root: already connected
4
uniondifferent roots: point one at the other
03

Complexity

best
O(1)find on a root
average
O(α(n))amortised, with compression and rank
worst
O(log n)one find, union by rank only
space
O(n)one parent per node
04

Versus its siblings

COST OF 1 000 000 UNION-FIND OPERATIONS
compression + rank
4M
compression only
6M
rank only
20M
naive linking
500B
pointer steps · naive linking degenerates to a long chain
05

Pseudocode

UNIONFIND(graph)
  parent[node] ← node for every node
  FIND(node): follow parent pointers to the root, then point every node on the way straight at it
  for each edge from–to: if FIND(from) ≠ FIND(to): parent[FIND(from)] ← FIND(to)
  the components are the distinct roots
06

When to use

Connectivity that only grows: Kruskal's MST, network percolation, image segmentation, friend circles.
Equivalence classes in compilers and type inference, where unification is a union.
07

Pitfalls

It cannot delete an edge; for dynamic connectivity with removals use a different structure.
Without compression or rank a chain of n unions makes find O(n).
Recursive find with compression can overflow on a degenerate chain; iterate with a second pass instead.
08

History

Bernard Galler and Michael Fischer described the forest representation in 1964. Robert Tarjan proved the inverse-Ackermann bound in 1975 and, with Jan van Leeuwen in 1984, showed that no pointer-based structure can do better, one of the few tight amortised results in the field.

Back to Graphs