<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. DSA
  3. Graphs
All lessons in this course

Lesson 38 of 50 13 minAdvanced

Union-find (disjoint sets)

Track which items belong to the same group as groups merge, with a parent array, union by rank and path compression, in almost O(1) per operation.

Beams get bolted together one connection at a time. After each new connection you want to know: are beam 3 and beam 8 now part of the same assembly? How many separate assemblies are there? Re-running BFS after every connection would be slow. Union-find (also called a disjoint-set union, DSU) answers these questions in practically constant time while the groups keep merging.

Two operations

  • Find(x): which group is x in? Returns a representative item (the group’s “root”). Two items are in the same group if and only if they have the same root.
  • Union(x, y): merge the groups of x and y into one.

Each group is stored as a tree, but all you keep is one array: parent[i] is the item above i, and a root is its own parent. Find walks up the parents to the root. Union finds both roots and makes one the parent of the other: one array write.

Interactive

Union-find

Ten beams, each in its own group. Click two beams, then Union to merge their groups. Each group is a tree; its root is the group’s name. parent[i] is all that is stored.

i
parent0123456789
rank0000000000
0123456789
Groups 10 · Tallest tree 0 links
Click one beam (amber), then another (blue), then press Union.

Two tricks that make it fast

Done naively, unions can build a long chain, and Find becomes O(n). Two simple improvements fix that:

  • Union by rank (or size): attach the shorter tree under the taller one, never the other way round. Trees then stay at most log₂ n tall.
  • Path compression: after Find walks up to the root, point every item on that path directly at the root. The next Find for any of them is one step.

With both, a sequence of m operations takes O(m · α(n)), where α is the inverse Ackermann function. It grows so slowly that α(n) ≤ 4 for any n you could ever store. In practice: constant time.

DisjointSet.cs
var beams = new DisjointSet(10);int[][] bolts = { new[] { 1, 2 }, new[] { 3, 4 }, new[] { 2, 4 }, new[] { 5, 6 }, new[] { 7, 8 }, new[] { 6, 8 } };foreach (var b in bolts) beams.Union(b[0], b[1]); Console.WriteLine($"Assemblies: {beams.Count}");Console.WriteLine($"1 and 3 connected? {beams.Connected(1, 3)}");Console.WriteLine($"1 and 5 connected? {beams.Connected(1, 5)}");Console.WriteLine($"Union(4, 4) merged anything? {beams.Union(4, 4)}"); class DisjointSet{    private readonly int[] _parent, _rank;    public int Count { get; private set; }          // number of separate groups     public DisjointSet(int n)    {        _parent = new int[n];        _rank = new int[n];        for (int i = 0; i < n; i++) _parent[i] = i;  // everyone starts alone        Count = n;    }     public int Find(int x)    {        if (_parent[x] != x)            _parent[x] = Find(_parent[x]);           // path compression        return _parent[x];    }     public bool Union(int a, int b)    {        int ra = Find(a), rb = Find(b);        if (ra == rb) return false;                  // already in the same group        if (_rank[ra] < _rank[rb]) (ra, rb) = (rb, ra);        _parent[rb] = ra;                            // shorter tree under the taller one        if (_rank[ra] == _rank[rb]) _rank[ra]++;        Count--;        return true;    }     public bool Connected(int a, int b) => Find(a) == Find(b);}
Output
Assemblies: 4
1 and 3 connected? True
1 and 5 connected? False
Union(4, 4) merged anything? False

Ten beams, six bolts, four assemblies: {0}, {1, 2, 3, 4}, {5, 6, 7, 8} and {9}. Union returning false is a useful signal on its own: it means the two items were already connected, so this new connection closes a loop.

Where union-find is used

  • Kruskal’s MST: “are the ends of this edge already connected?”
  • Cycle detection in undirected graphs as edges arrive.
  • Dynamic connectivity: network links coming up one by one, “are these two computers connected yet?”
  • Grouping equal things: merging duplicate customer records that share an email or phone number, or labelling connected regions in an image.
Quick check

parent = { 0, 0, 1, 2 } (no path compression yet). What does Find(3) return, and how many steps does it take?

Quick check

Why attach the shorter tree under the taller one in Union?

Practice

Connections between beams arrive one by one. Return the first connection that closes a loop (both beams were already in the same assembly). For connections (1,2), (2,3), (3,4), (1,4), (1,5) the answer is (1, 4). Beams are numbered 1 to 5.

Show a hint

Use the DisjointSet class (size n + 1 so that numbers 1…n are valid indexes). Return the first connection for which Union returns false.

Show the solution
Solution.cs
int[][] links = { new[] { 1, 2 }, new[] { 2, 3 }, new[] { 3, 4 }, new[] { 1, 4 }, new[] { 1, 5 } };var loop = FirstLoop(5, links);Console.WriteLine(loop is null ? "no loop" : $"({loop[0]}, {loop[1]})"); static int[]? FirstLoop(int n, int[][] links){    var ds = new DisjointSet(n + 1);    foreach (var link in links)        if (!ds.Union(link[0], link[1])) return link;   // already connected: this closes a loop    return null;} class DisjointSet{    private readonly int[] _parent, _rank;    public DisjointSet(int n)    {        _parent = new int[n];        _rank = new int[n];        for (int i = 0; i < n; i++) _parent[i] = i;    }    public int Find(int x) => _parent[x] == x ? x : _parent[x] = Find(_parent[x]);    public bool Union(int a, int b)    {        int ra = Find(a), rb = Find(b);        if (ra == rb) return false;        if (_rank[ra] < _rank[rb]) (ra, rb) = (rb, ra);        _parent[rb] = ra;        if (_rank[ra] == _rank[rb]) _rank[ra]++;        return true;    }}
Output
(1, 4)

Remember

  • Union-find tracks merging groups with one parent array; a root represents its group.
  • Find walks up to the root; Union links one root under the other.
  • Union by rank plus path compression make both operations practically O(1).
  • Used by Kruskal, cycle detection, dynamic connectivity and record merging.
0 of 50 lessons completed