Skip to content
academia.sh

Lesson 10 / 26

Disjoint Sets

The dynamic grouping problem, the union-find structure, union by rank, path compression, and its uses.

Contents

Suppose the question is whether two computers in a network are connected — directly or through intermediaries. Connections are added over time and never removed. Two questions arrive in turn: “are these two in the same group?” and “merge these two groups.”

This is the problem solved by the disjoint sets structure — also known as union–find. Its name comes from the sets having no intersection: every element belongs to exactly one group.

Two Operations

Operation Meaning
find(x) Returns the representative of the group x belongs to
union(x, y) Merges the groups of the two elements into one

The “same group” question requires no separate operation: if two elements’ representatives are the same, they are in the same group.

A representative is any element of the group; which element it is does not matter — what matters is that every element in the same group finds the same representative.

Forest Representation

The structure keeps every group as a tree; the tree’s root is the group’s representative. All the groups together form a forest.

The representation is a single array: parent[x] holds x’s parent node. A root points to itself.

parent:  [0, 0, 1, 3, 3]
          ↑
          0 and 3 are roots (pointing to themselves)

group 1: 0 ← 1 ← 2        group 2: 3 ← 4

The find operation follows parent links until it reaches the root. union attaches one of two roots to the other.

In this plain form, the structure can degenerate in the worst case: if every union attaches a root to the end of a long chain, the tree turns into a linked list and find becomes O(n)O(n).

Two Improvements

Union by rank. During a union, the smaller tree’s root is attached to the larger one’s. This way, depth increases only when two trees of equal size merge; tree height is bounded by O(logn)O(\log n). Either the element count or an estimated height (rank) is used as the size measure. Both give the same bound; the element count is often preferred because it provides a side benefit — the group size can also be queried. Once path compression is applied, the estimated height can exceed the actual height — meaning the measure is used only in the union decision and does not need to be exact.

Path compression. As the find operation locates the root, it attaches every node along the way directly to the root. The next query does not retrace the same path. The operation flattens the structure as a byproduct of the query.

When both are applied together, the total cost of mm operations is nearly linear: the amortized cost per operation is bounded by a function that grows slowly enough to be treated as constant in practice. The precise statement and proof belong to the Advanced Algorithms course; what is enough for this course is that the two improvements are used together.

class DisjointSets:
    """Union-find with union by size and path compression."""

    def __init__(self, n: int) -> None:
        self._parent = list(range(n))    # initially, everyone is their own group
        self._size = [1] * n
        self.group_count = n

    def find(self, x: int) -> int:
        root = x
        while self._parent[root] != root:    # climb to the root
            root = self._parent[root]
        while self._parent[x] != root:        # path compression: attach the path to the root
            self._parent[x], x = root, self._parent[x]
        return root

    def union(self, x: int, y: int) -> bool:
        a, b = self.find(x), self.find(y)
        if a == b:
            return False                  # already in the same group
        if self._size[a] < self._size[b]:
            a, b = b, a                   # the larger tree becomes the root
        self._parent[b] = a
        self._size[a] += self._size[b]
        self.group_count -= 1
        return True

    def same_group(self, x: int, y: int) -> bool:
        return self.find(x) == self.find(y)


ds = DisjointSets(6)                      # six elements, 0..5
ds.union(0, 1)
ds.union(1, 2)
ds.union(3, 4)

print(ds.same_group(0, 2))                # True
print(ds.same_group(0, 3))                # False
print(ds.group_count)                     # 3   — {0,1,2}, {3,4}, {5}

print(ds.union(0, 2))                     # False — already together
ds.union(2, 4)
print(ds.same_group(1, 3), ds.group_count)   # True 2

The union operation returning a logical value is a practical detail: the caller learns whether a merge actually took place. This information is used directly in the applications covered in the next section.

Tracing

To see how the structure flattens, the parent array can be examined step by step:

ds = DisjointSets(5)
ds.union(1, 2)
ds.union(3, 4)
ds.union(2, 4)
print(ds._parent)             # [0, 1, 1, 1, 3]  — 3's root is still indirect

ds.find(4)                    # path compression runs
print(ds._parent)             # [0, 1, 1, 1, 1]  — 4 is now attached directly to the root

In the first output, node 4’s root was found in two steps (4 → 3 → 1); after a single find call, the link points directly to the root. Compression is not a separate maintenance operation — the query itself improves the structure.

What It Cannot Do

The structure’s well-known limit is that splitting is not supported. Groups merge, they do not split. When a connection is removed, the groups must be recomputed; this means building the structure from scratch.

The second limit is that group members cannot be listed. The structure only answers the “same group” question; counting or listing a group’s elements requires keeping additional records.

These constraints are not accidental: the structure’s speed comes precisely from not keeping this information.

Applications

Connected components. Which nodes in a graph can reach each other is tracked with this structure as edges are added. In this course’s last topic, the same question will also be answered with traversal algorithms; disjoint sets are more suitable when edges arrive as a stream.

Minimum spanning tree. The algorithm that sorts edges by weight and selects the ones that do not form a cycle asks the “does this edge form a cycle” question with disjoint sets: if the two endpoints are already in the same group, the edge is skipped. The algorithm itself is covered in the Algorithms course.

Equivalence classes. Groups formed by accumulating rules of the “these two count as the same” kind — merging accounts belonging to the same person, matching records that refer to the same object — are held directly with this structure.

Grid problems. Regions formed by merging adjacent cells in a grid (connected areas in an image, percolation models) are tracked the same way.

Another property of the structure is that its result is independent of the order of operations: no matter what order the same set of unions is applied in, the resulting groups are the same. Only the internal shape of the trees changes, and this does not affect the result of any query.

Cost Table

Implementation find union
Plain forest O(n)O(n) O(n)O(n)
Union by size O(logn)O(\log n) O(logn)O(\log n)
+ path compression Amortized nearly constant Amortized nearly constant

Summary

  • The disjoint-set structure divides elements into non-intersecting groups and offers two operations: finding a group’s representative and merging two groups.
  • Groups are kept as a forest; the root is the group’s representative.
  • Union by size keeps the height logarithmic by attaching the smaller tree to the larger one.
  • Path compression flattens the structure during find by attaching the nodes along the path directly to the root.
  • Together, the two give an amortized, nearly constant cost per operation.
  • The structure does not support splitting or listing a group’s elements; its speed comes from not keeping this information.

Next Step

This topic used a tree representation for groups, but trees themselves were never defined. The next topic covers trees on their own: terminology, traversal methods, search trees, the balance problem, and the heap structure that implements a priority queue.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close