A network receives connections over time and repeatedly asks whether two nodes belong to the same connected component. Running a graph traversal for every query revisits edges whose connectivity was already established. A disjoint set keeps only the partition of nodes into components, so a merge and a connectivity check become nearly constant-time operations.

The structure is narrower than a graph representation. It remembers which elements belong together, but not the edges, paths, or order that produced each component. Sets can merge; they cannot be split efficiently afterward.

Core shape: elements → parent-index forest → one root per set → shared root means shared membership → O(n) storage.

State across operations

The trace starts with seven singleton sets. The first three unions deliberately create the chain 0 → 1 → 2 → 3; find(0) then rewrites the visited parents to point directly at root 3.

Only roots are linked during a union. Linking an arbitrary interior node would detach or misclassify part of its existing set. A find follows parent indices until parent[root] == root; path compression can then shorten the route without changing the representative.

The trace uses direct root linking to make a deep chain and its compression visible. The reference implementation also stores rank, preventing that chain from becoming deep in the first place.

Representation and invariants

Each element is mapped to an integer index. Two parallel arrays hold the state:

  • _parent[i] stores the next index on the path to the representative. A root points to itself.
  • _rank[i] approximates tree height and is meaningful only for roots. _size[i] is a common alternative when component counts are needed.

Four invariants define a valid state:

  1. Every parent index is inside the array.
  2. Following parent indices always reaches a self-parented root; cycles other than that self-reference are invalid.
  3. Two elements are in the same set exactly when their root is the same.
  4. The merge link changes the parent of one root. Any interior-parent rewrites come only from the path-compressing finds that locate both roots.

Path compression rewrites parent indices but preserves set membership. Union by rank changes which root represents the merged set but preserves every previous connectivity result. The representative is therefore an internal identity, not a stable domain value.

Complexity

OperationBest timeAmortized timeWorst single operationPeak space
Construct n singleton setsΘ(n)Θ(n)Θ(n)Θ(n) structure
Find(x)O(1)O(α(n))O(log n)O(1) best, O(log n) worst stack
Union(a, b)O(1)O(α(n))O(log n)O(1) best, O(log n) worst stack
Connected(a, b)O(1)O(α(n))O(log n)O(1) best, O(log n) worst stack

These bounds assume path compression and union by rank. Rank alone keeps tree height at O(log n); path compression makes a sequence of operations cost O(α(n)) per operation amortized. Without either heuristic, a chain can grow to length n, turning Find, Union, and Connected into O(n) operations.

α(n) is the inverse Ackermann function and stays below 5 for practical input sizes. “Amortized” matters more than an informal average here: an individual operation can traverse several parents, while the rewrites make later operations cheaper.

The recursive implementation uses stack space proportional to the current tree height. An iterative path-halving implementation reduces auxiliary space to O(1) while keeping the same amortized time bound.

When the structure stops fitting

Deletion is the hard boundary. After several unions and path-compressing finds, the structure no longer records which original edge caused a component to form. Removing an edge therefore cannot identify whether the component should stay connected or split. Fully dynamic connectivity needs a graph representation plus a more complex dynamic structure; a known offline sequence can use rollback DSU without path compression.

Connectivity also carries no route information. Connected(a, b) can return true, but the parent forest is an implementation artifact rather than a path through the original graph. Shortest paths, neighbors, degrees, and edge metadata require an adjacency representation alongside the disjoint set.

The array representation assumes dense integer IDs from 0 through n - 1. Strings, GUIDs, and sparse numeric IDs need a Dictionary<T, int> mapping before they can enter the structure. That mapping adds memory and makes identity management part of the API boundary.

Reference drawer

Comparison

RepresentationConnectivity queryAdd connection / mergeRemovalInformation retainedStronger case
Disjoint setO(α(n)) amortizedO(α(n)) amortizedNot supportedComponent membershipConnections only accumulate and connectivity is queried repeatedly
Static component labelsO(1) after preprocessingRecompute labels in O(V + E)Recompute labelsComponent ID snapshotThe graph is immutable and receives many connectivity queries
Rollback disjoint setO(log n)O(log n)O(1) rollback of the latest mergeComponent membership plus change historyOffline connectivity where additions must be undone in reverse order

The disjoint set occupies a specific point in this comparison: it gives up graph topology and deletion in exchange for extremely cheap incremental merges and membership checks. Static labels are cheaper to query when the graph never changes. Rollback retains change history at a higher per-operation cost and without path compression.

The related Union-Find note covers the operation heuristics and their analysis. This page remains centered on stored state, invariants, and the boundary of the data structure itself.

Questions

References