The union-find data structure is used to represent disjoint subsets of a family of elements. As its name suggests, it supports two main operations: union and find. Union returns the combination of two subsets. Find returns the representative element of a subset for any element (each subset nominates one of its members to be its representative). First, create singleton subsets for every element in the family. Next, call union until the family is represented as desired. For example, when creating a minimum spanning tree, union the nodes into the tree as it grows or begin the next tree in the forest as a singleton. In a naive implementation, the time complexity of find is $\theta(m \times n)$, which is not good. In union by rank, the lowest rank tree becomes the child of the highest ranked tree. The depth of the resulting tree does not grow unless the two trees have the same depth, in which case it grows by only $1$. With union by rank, the trees are more balanced and thus the runtime is reduced to closer to $\theta(log_2(n))$. A further strategy is rank compression, where whenever we perform a find, we connect all the nodes along the path from the query node to the root directly to the root, which reduces the runtime of $m$ find operations to $n \alpha(m)$ where $\alpha$ is the Ackermann function which grows very slowly as a function of $m$ ($\alpha(10^{80})\le 4$).