Union Find Disjoint Set Data Structure in DSA C++ - Time & Space Complexity
We want to understand how fast the Union Find data structure works when connecting or checking groups.
How does the time needed change as we add more items?
Analyze the time complexity of the following code snippet.
class UnionFind {
std::vector<int> parent, rank;
public:
UnionFind(int n) : parent(n), rank(n, 0) {
for (int i = 0; i < n; i++) parent[i] = i;
}
int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
void unite(int a, int b) {
a = find(a); b = find(b);
if (a != b) {
if (rank[a] < rank[b]) parent[a] = b;
else if (rank[b] < rank[a]) parent[b] = a;
else {
parent[b] = a;
rank[a]++;
}
}
}
};
This code manages groups of items, quickly joining two groups or checking if two items share a group.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The
findfunction uses recursion to find the group's leader. - How many times: Each
findcall may recurse up the tree, but path compression shortens future calls. - Union operation: Calls
findtwice and then merges groups, which is a constant-time step.
As the number of items grows, each find call takes slightly longer at first but quickly becomes very fast due to path compression.
| Input Size (n) | Approx. Operations per find/unite |
|---|---|
| 10 | About 2-3 steps |
| 100 | About 4-5 steps |
| 1000 | About 5-6 steps |
Pattern observation: The steps grow very slowly, almost like a flat line, thanks to path compression and rank balancing.
Time Complexity: O(α(n))
This means each operation runs almost in constant time, where α(n) is a very slow-growing function that is practically constant for all reasonable input sizes.
[X] Wrong: "Each find operation takes linear time because it might traverse many parents."
[OK] Correct: Path compression flattens the structure, so repeated finds become very fast, avoiding long traversals.
Understanding this time complexity shows you how efficient group management can be, a skill useful in many coding problems and real-world applications.
"What if we removed path compression from the find function? How would the time complexity change?"
Practice
Union Find Disjoint Set data structure?Solution
Step 1: Understand the role of Union Find
The Union Find data structure is designed to keep track of which elements belong to which groups or sets, especially when these groups can merge.Step 2: Compare with other data structures
Sorting elements, storing key-value pairs, or implementing priority queues are different tasks handled by other data structures.Final Answer:
To efficiently track and merge groups of connected elements -> Option AQuick Check:
Union Find = Track and merge groups [OK]
- Confusing Union Find with sorting algorithms
- Thinking it stores key-value pairs like a map
- Assuming it manages priority like a heap
n in C++?Solution
Step 1: Initialize parent array correctly
The parent array should have size n, and each element should initially be its own parent, so indices 0 to n-1 must be set.Step 2: Check loop boundaries
Loop must run from 0 to n-1 (i < n) to avoid out-of-bounds errors.Final Answer:
int parent[n]; for(int i=0; i -> Option BQuick Check:
Initialize parents from 0 to n-1 [OK]
- Using <= n causing out-of-bounds error
- Starting loop from 1 when array is 0-indexed
- Declaring array size incorrectly
int parent[5];
for(int i=0; i<5; i++) parent[i] = i;
// Union function
void unionSet(int a, int b) {
int rootA = findSet(a);
int rootB = findSet(b);
if(rootA != rootB) parent[rootB] = rootA;
}
// Find function
int findSet(int x) {
if(parent[x] == x) return x;
return parent[x] = findSet(parent[x]);
}
unionSet(0, 1);
unionSet(1, 2);
unionSet(3, 4);
cout << (findSet(2) == findSet(0)) << " " << (findSet(4) == findSet(0));
What is the output?Solution
Step 1: Perform unions and find roots
unionSet(0,1) merges sets of 0 and 1; unionSet(1,2) merges sets of 1 and 2, so 0,1,2 are connected. unionSet(3,4) connects 3 and 4.Step 2: Check if 2 and 0 share the same root, and if 4 and 0 share the same root
findSet(2) == findSet(0) is true (1), findSet(4) == findSet(0) is false (0).Final Answer:
1 0 -> Option DQuick Check:
Connected(2,0) = 1, Connected(4,0) = 0 [OK]
- Assuming 4 is connected to 0
- Forgetting path compression in findSet
- Mixing up union order
int parent[5];
for(int i=0; i<5; i++) parent[i] = i;
int findSet(int x) {
if(parent[x] == x) return x;
return findSet(parent[x]);
}
void unionSet(int a, int b) {
int rootA = findSet(a);
int rootB = findSet(b);
if(rootA != rootB) parent[rootB] = rootA;
}
unionSet(0, 1);
unionSet(1, 2);
cout << (findSet(2) == findSet(0));
Solution
Step 1: Analyze findSet function
The findSet function recursively finds the root but does not update parent[x] to root, missing path compression optimization.Step 2: Understand impact of missing path compression
Without path compression, findSet is slower on deep trees but still correct. Other options are incorrect because initialization and union checks are correct, and array size matches elements.Final Answer:
Missing path compression in findSet function -> Option CQuick Check:
findSet lacks path compression [OK]
- Thinking union lacks root equality check
- Assuming parent array is wrongly initialized
- Confusing array size with element count
unionSet(0, 1); unionSet(2, 3); unionSet(4, 5); unionSet(1, 2); unionSet(0, 4);How many distinct sets remain?
Solution
Step 1: Track unions step-by-step
Initially: {0}, {1}, {2}, {3}, {4}, {5} unionSet(0,1) -> {0,1}, {2}, {3}, {4}, {5} unionSet(2,3) -> {0,1}, {2,3}, {4}, {5} unionSet(4,5) -> {0,1}, {2,3}, {4,5} unionSet(1,2) merges {0,1} and {2,3} -> {0,1,2,3}, {4,5} unionSet(0,4) merges {0,1,2,3} and {4,5} -> {0,1,2,3,4,5}Step 2: Count remaining distinct sets
All elements are connected in one set after last union.Final Answer:
1 -> Option AQuick Check:
All merged into one set [OK]
- Counting sets before all unions
- Not merging transitive connections
- Assuming unions only connect pairs
Ready to prove it?
Pass the test and this pattern moves unknown
