Hello! In our last lesson, we mastered the Bellman-Ford algorithm, a robust tool for finding shortest paths in graphs with negative edges. We concluded by noting that our next topic, Minimum Spanning Trees (MSTs), would require a specialized data structure for efficiently detecting cycles. This lesson introduces that very structure.
Today, we will focus on the learning outcome: Describe the union-find data structure with path compression and union by rank.
Also known as a Disjoint Set Union (DSU), this data structure is fundamental in computer science. It's designed to manage a collection of items partitioned into a number of non-overlapping (disjoint) sets. Its primary use case, and the one most relevant to our upcoming lessons, is to very quickly determine if two items belong to the same set and to merge two sets. Its incredible efficiency makes it a key component in algorithms like Kruskal's for finding MSTs.
1. The Core Problem and a Naive Approach
Imagine you have a set of elements, and you want to group them into components. You need to be able to perform two main operations:
- Find: Check which component (or set) an element belongs to.
- Union: Merge two components into a single one.
A common application is tracking connected components in a graph. Initially, each vertex is its own component. As you add an edge between two vertices, you union their components. At any point, you can use find to check if two vertices are already connected.
Let's start by watching a video that introduces this concept and its applications in dynamic graphs.
G-46. Disjoint Set | Union by Rank | Union by Size | Path Compression
The video 'Disjoint Set | Union by Rank | Union by Size | Path Compression' by take U forward provides an excellent high-level introduction to what a Disjoint Set is and why it's useful.
Please watch from the beginning to 05:13. Focus on understanding the problem DSU solves: efficiently tracking set membership and performing unions, especially in scenarios where the connections (the graph) are changing.
A simple way to implement this is to represent our sets as a forest of trees, where each tree corresponds to a set. We can use a simple array, let's call it parent, where parent[i] stores the parent of element i. The root of a tree is the representative of its set, and it's identified by the condition parent[i] == i.
make_set(i):parent[i] = i. Each element starts as its own set.find_set(i): Starting fromi, traverse up the tree by following parent pointers until you reach the root.union_sets(a, b): Find the root ofaand the root ofb. If they are different, make one root the parent of the other.
This naive approach has a significant flaw. A sequence of unions could create a degenerate tree that looks like a long chain. In this worst-case scenario, a find_set operation would take time, as it would have to traverse the entire chain. To achieve the high efficiency that makes DSU so powerful, we need a couple of clever optimizations.
2. Optimization 1: Union by Rank
The first optimization addresses how we perform the union operation. To prevent trees from becoming unnecessarily tall, we should be strategic about which tree we attach to the other. The union by rank heuristic does just that.
The "rank" of a root node is an upper bound on the height of its tree. When we union two sets, the rule is: always attach the root of the tree with the lower rank to the root of the tree with the higher rank.
- If ranks are different, the rank of the new root doesn't change.
- If ranks are the same, we can attach either way, and we increment the rank of the new root by one.
This simple rule helps keep the trees balanced and shallow.

To see a step-by-step walkthrough and understand the intuition, let's return to the "take U forward" video.
G-46. Disjoint Set | Union by Rank | Union by Size | Path Compression
These segments of the video explain the Union by Rank heuristic. The first part shows it in action, and the second part explains the logic behind why we attach the smaller tree to the larger one.
First, watch from 06:39 to 12:50 to see a detailed example of how Union by Rank works. Then, watch from 32:29 to 35:02 for a clear explanation of why this heuristic is effective at minimizing tree depth.
An alternative, equally effective heuristic is Union by Size, where you track the number of elements in each set and always attach the smaller set to the larger one. The principle is the same: minimize the "effort" required to update parent pointers and keep the resulting tree as balanced as possible.
3. Optimization 2: Path Compression
The second optimization, path compression, dramatically speeds up the find_set operation. The idea is that whenever we traverse a path from a node up to the root to find the set's representative, we can flatten that path along the way.
Specifically, after a call to find_set(i) returns the root r, we make every node on the path from i to r point directly to r.

This optimization doesn't change the correctness of find_set—it still returns the same root—but it makes the tree much flatter, benefiting subsequent calls. Path compression is often implemented elegantly using recursion.
Let's see a final video segment that explains this technique.
G-46. Disjoint Set | Union by Rank | Union by Size | Path Compression
This part of the video explains and demonstrates the path compression technique.
Watch from 12:50 to 19:19. The video explains the motivation for path compression and shows how a recursive find_parent function can implement it naturally.
4. Implementation and Complexity
With both optimizations—union by rank/size and path compression—the DSU data structure becomes incredibly fast. Let's look at the C++ implementation, which is quite concise.
The article 'Disjoint Set Union' from cp-algorithms.com provides clean, minimal implementations of the optimized DSU operations. This will help you consolidate your understanding of the logic.
Read the sections 'Path compression optimization' and 'Union by size / rank'. Focus on the C++ code snippets. Notice how path compression is implemented with a single recursive line in find_set and how union_sets incorporates the rank/size check.
The time complexity analysis of DSU is famous in computer science. When both optimizations are used, the amortized time per operation is , where is the inverse Ackermann function.
This function grows so slowly that for any practical value of you would encounter (e.g., ), is less than 5. Therefore, for all practical purposes, the DSU operations can be considered to run in nearly constant time. This remarkable efficiency is why DSU is the go-to data structure for problems involving disjoint sets.
Conclusion
In this lesson, we've unpacked the Union-Find data structure, an essential tool for managing disjoint sets. You learned not just how it works, but why it's so efficient.
Key Takeaways:
- A Disjoint Set Union (DSU) or Union-Find data structure tracks a collection of disjoint sets and supports efficient
unionandfindoperations. - It is commonly implemented as a forest of trees, where each tree represents a set and its root is the set's representative.
- A naive implementation can lead to complexity for operations due to tall, chain-like trees.
- The union by rank (or size) heuristic combats this by always attaching the smaller tree to the root of the larger tree during a union, keeping the trees shallow.
- Path compression further optimizes the structure by flattening the tree during
findoperations, making all nodes on a lookup path point directly to the root. - Together, these optimizations give DSU a nearly constant amortized time complexity, .
Preview of the Next Lesson:
Now that we have mastered the Union-Find data structure, we are ready to apply it. In the next lesson, we will learn Kruskal's algorithm, a classic greedy algorithm for finding a Minimum Spanning Tree. You will see firsthand how Union-Find's ability to efficiently detect cycles is the cornerstone of this algorithm.