AI InterviewSearch…Search problems, topics, or pages…Ctrl KGet started
Skip to content

07 Disjoint set union (DSU) / Lesson 1 of 1

Disjoint set union — union by size and path compression

14 min · +10 XP · 2 exercises · 4 questions

Read the text14 min
Exercises0/2
Quiz4 q

Quick answer

What is a disjoint set union (DSU)?

A disjoint set union (DSU), also called union-find, keeps elements split into groups and answers "are a and b in the same group?" while groups merge. Each group is a tree stored in a parent array; find walks to the root that names the group and unite hangs one root under the other. With union by size and path compression both run in almost constant amortised time.

A DSU answers one question fast while groups keep merging: are a and b connected yet? Contest uses: components after each edge, cycle detection, Kruskal's minimum spanning tree, offline "merge these, then ask".

The structure

  • Each group is a tree; p[x] is one step from x towards its root, and a root is its own parent.
  • The root names the group: a and b are together exactly when find(a) == find(b).
  • sz[r] is the number of elements under root r — read only at a root.
struct DSU {
    vector<int> p, sz;
    DSU(int n) : p(n), sz(n, 1) { iota(p.begin(), p.end(), 0); }
    int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
    bool unite(int a, int b) {
        a = find(a), b = find(b);
        if (a == b) return false;          // already together
        if (sz[a] < sz[b]) swap(a, b);     // a is the larger root
        p[b] = a;
        sz[a] += sz[b];
        return true;
    }
};

Two rules keep it fast

  • Union by size: the smaller root goes under the larger. An element only sinks one level when its group at least doubles, so a tree is never taller than log2 n.
  • Path compression: p[x] = find(p[x]) re-points every node it walks straight at the root.
  • Together: amortised O(α(n)) per call — effectively constant. Either one alone is already O(log n).
Union by size and path compression
unite(0,1), unite(2,3), unite(0,2), unite(4,5), unite(4,6), unite(3,6), find(5)

Seven elements, seven sets: every element is its own root, so parent[i] = i and size[i] = 1. The size row is only read at a root.

0:01 / 0:28Step 1 of 10
Transcript · 10 steps

Contest habits

  • unite returning false is the cycle test: the edge joined two vertices that were already connected.
  • Keep a running components counter: start at n, subtract one on every successful unite.
  • The recursive find is fine at 2 × 10^5 with union by size (depth ≤ 18). Without it, a chain of 10^6 can overflow the stack.
  • Index from 0 or convert 1-based input once at read time — never mix.

More practice

Common questions

What is path compression in union-find?

During find(x), every node on the walk from x to its root is re-pointed straight at the root, so the next find on any of them takes one step. It is one line in the recursive form: return p[x] == x ? x : p[x] = find(p[x]);.

Why use union by size or by rank?

Hanging the smaller tree under the larger one means an element only gets one link deeper when its group at least doubles, so no tree is ever taller than log2 n. Without it, uniting in a chain order builds a path of length n and every find costs O(n).

What is the time complexity of DSU with both optimisations?

O(α(n)) amortised per operation, where α is the inverse Ackermann function — at most 4 for any input that fits in memory. In practice, treat each find and unite as O(1); a contest with 2 × 10^5 operations runs in milliseconds.

How do you detect a cycle in an undirected graph with DSU?

Add the edges one at a time. Before uniting the endpoints of an edge, call find on both: if they already have the same root, the endpoints were connected before this edge, so the edge closes a cycle.

Mark as readSign in to keep your place.

Disjoint Set Union in C++: Union by Size, Path Compression

DSU (union-find) in C++ for contests: a parent array, find with path compression, union by size, and the near-O(1) cost that makes connectivity queries cheap.

What is a disjoint set union (DSU)?

A disjoint set union (DSU), also called union-find, keeps elements split into groups and answers "are a and b in the same group?" while groups merge. Each group is a tree stored in a parent array; find walks to the root that names the group and unite hangs one root under the other. With union by size and path compression both run in almost constant amortised time.

Lesson

A DSU answers one question fast while groups keep merging: are a and b connected yet? Contest uses: components after each edge, cycle detection, Kruskal's minimum spanning tree, offline "merge these, then ask".

The structure

  • Each group is a tree; p[x] is one step from x towards its root, and a root is its own parent.
  • The root names the group: a and b are together exactly when find(a) == find(b).
  • sz[r] is the number of elements under root r — read only at a root.
struct DSU {
    vector<int> p, sz;
    DSU(int n) : p(n), sz(n, 1) { iota(p.begin(), p.end(), 0); }
    int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
    bool unite(int a, int b) {
        a = find(a), b = find(b);
        if (a == b) return false;          // already together
        if (sz[a] < sz[b]) swap(a, b);     // a is the larger root
        p[b] = a;
        sz[a] += sz[b];
        return true;
    }
};

Two rules keep it fast

  • Union by size: the smaller root goes under the larger. An element only sinks one level when its group at least doubles, so a tree is never taller than log2 n.
  • Path compression: p[x] = find(p[x]) re-points every node it walks straight at the root.
  • Together: amortised O(α(n)) per call — effectively constant. Either one alone is already O(log n).
0123456parent00010203040546size7······sets: 1 · tallest tree: 2 links
Union by size and path compression. Example: unite(0,1), unite(2,3), unite(0,2), unite(4,5), unite(4,6), unite(3,6), find(5)
  1. Seven elements, seven sets: every element is its own root, so parent[i] = i and size[i] = 1. The size row is only read at a root.
  2. unite(0, 1): Both sets have size 1, a tie, so 0 stays the root and size[0] becomes 2.
  3. unite(2, 3): Both sets have size 1, a tie, so 2 stays the root and size[2] becomes 2.
  4. unite(0, 2): Both sets have size 2, a tie, so 0 stays the root and size[0] becomes 4. Now 3 sits two links below its root — the only kind of path compression can shorten.
  5. unite(4, 5): Both sets have size 1, a tie, so 4 stays the root and size[4] becomes 2.
  6. unite(4, 6): The set of 6 is smaller, so its root 6 goes under 4 and size[4] becomes 3.
  7. unite(3, 6) starts with find(3), which walks 3 → 2 → 0 to the root 0.
  8. Path compression: on the way back every node of that walk points straight at 0, so parent[3] is now 0 and the next find(3) is one step.
  9. find(6) is 6 → 4. The set of 0 has 4 elements and the set of 4 has 3, so 4 goes under 0: one set of 7.
  10. A later find(5) walks 5 → 4 → 0 once and flattens it. Union by size keeps every tree at most log2 n tall; path compression flattens what is walked, and together a find costs almost O(1).

Contest habits

  • unite returning false is the cycle test: the edge joined two vertices that were already connected.
  • Keep a running components counter: start at n, subtract one on every successful unite.
  • The recursive find is fine at 2 × 10^5 with union by size (depth ≤ 18). Without it, a chain of 10^6 can overflow the stack.
  • Index from 0 or convert 1-based input once at read time — never mix.

More practice

Common questions

What is path compression in union-find?

During find(x), every node on the walk from x to its root is re-pointed straight at the root, so the next find on any of them takes one step. It is one line in the recursive form: return p[x] == x ? x : p[x] = find(p[x]);.

Why use union by size or by rank?

Hanging the smaller tree under the larger one means an element only gets one link deeper when its group at least doubles, so no tree is ever taller than log2 n. Without it, uniting in a chain order builds a path of length n and every find costs O(n).

What is the time complexity of DSU with both optimisations?

O(α(n)) amortised per operation, where α is the inverse Ackermann function — at most 4 for any input that fits in memory. In practice, treat each find and unite as O(1); a contest with 2 × 10^5 operations runs in milliseconds.

How do you detect a cycle in an undirected graph with DSU?

Add the edges one at a time. Before uniting the endpoints of an edge, call find on both: if they already have the same root, the endpoints were connected before this edge, so the edge closes a cycle.

Exercises

Components after every union

There are n people, numbered 1 to n, each in a group of their own. q times, two people u and v join groups (if they are already in the same group, nothing changes). After every join, print two numbers: how many groups there are, and the size of the largest group.

Input: n q, then q lines u v (1 ≤ n ≤ 2·10^5, 1 ≤ q ≤ 2·10^5, 1 ≤ u, v ≤ n; u = v is allowed). Output: q lines <groups> <largest>.

6 5
1 2
3 4
2 1
2 4
5 6

prints

5 2
4 2
4 2
3 4
2 4

The first edge that closes a cycle

An undirected graph on vertices 1 to n receives m edges one at a time. Print the 1-based index of the first edge whose two endpoints were already connected before it arrived — the first edge that closes a cycle. A self-loop v v closes a cycle at once. Print -1 when no edge does.

Input: n m, then m lines u v (1 ≤ n ≤ 2·10^5, 0 ≤ m ≤ 2·10^5). Output: one integer.

5 6
1 2
2 3
4 5
3 4
1 3
2 5

prints

5

Edge 5 (1 3) arrives after 1 2 and 2 3 have already connected 1 and 3.

In this module: Disjoint set union (DSU)

← Checkpoint — Number theory · Checkpoint — Disjoint set union →