Edges are added to a graph and at any moment we ask: are a and b in the same component? Running BFS/DFS each time costs O(V + E). Union-Find (Disjoint Set Union, DSU) keeps the components as sets and answers this almost in O(1).
The idea: a forest
Every set is a tree and the root of the tree is the representative of the set. The forest lives in one array: parent[i] is the parent of i, and r is a root when parent[r] == r. find(x) walks up to the root and union(a, b) hangs one root under the other.
If we always hang the same side the tree degenerates into a chain and find becomes O(n). Below is the difference on 1000 consecutive unions.
def depth(parent, x):
d = 0
while parent[x] != x:
x, d = parent[x], d + 1
return d
def find(parent, x):
while parent[x] != x:
x = parent[x]
return x
n = 1000
naive = list(range(n))
by_size, size = list(range(n)), [1] * n
for i in range(n - 1):
naive[find(naive, i)] = find(naive, i + 1) # always hang the left root
a, b = find(by_size, i), find(by_size, i + 1)
if size[a] < size[b]:
a, b = b, a
by_size[b] = a # small tree under the large
size[a] += size[b]