Union by size attaches the smaller tree under the larger tree:
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.size = [1] * n
def union(self, x, y):
rootX, rootY = self.find(x), self.find(y)
if rootX == rootY:
return
if self.size[rootX] < self.size[rootY]:
self.parent[rootX] = rootY
self.size[rootY] += self.size[rootX]
else:
self.parent[rootY] = rootX
self.size[rootX] += self.size[rootY]
Size is often more useful than rank because it tells you the actual component size—useful for problems that ask "how many nodes in this component?"
Both rank and size achieve tree height without path compression, and with path compression.