Union Find Disjoint Set Data Structure in DSA Go - Time & Space Complexity
We want to understand how fast the Union Find data structure works when connecting groups and checking if items belong together.
How does the time needed change as we add more items and connections?
Analyze the time complexity of the following code snippet.
// Find returns the root parent of x with path compression
func (uf *UnionFind) Find(x int) int {
if uf.parent[x] != x {
uf.parent[x] = uf.Find(uf.parent[x])
}
return uf.parent[x]
}
// Union connects two sets if they are different
func (uf *UnionFind) Union(x, y int) {
rootX := uf.Find(x)
rootY := uf.Find(y)
if rootX != rootY {
uf.parent[rootY] = rootX
}
}
This code finds the group leader of an item and connects two groups if they are not already connected.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The recursive Find calls that follow parent links to find the root.
- How many times: At most, it follows the chain of parents up to the root, which can be up to n in worst case but usually much less.
As the number of items n grows, each Find operation takes longer if the parent chains are long.
| Input Size (n) | Approx. Operations per Find |
|---|---|
| 10 | Up to 10 steps, usually fewer |
| 100 | Up to 100 steps, usually fewer |
| 1000 | Up to 1000 steps, usually fewer |
Pattern observation: Without improvements, Find can take longer as n grows, but with path compression, the chains become very short, making operations almost constant time.
Time Complexity: O(α(n))
This means each operation runs in almost constant time, where α(n) is a very slowly growing function that is practically constant for all reasonable n.
[X] Wrong: "Each Find or Union operation always takes linear time O(n)."
[OK] Correct: Because path compression keeps the trees flat, making operations almost constant time, not linear.
Understanding this complexity shows you know how efficient grouping works behind the scenes, a skill useful in many problems involving connectivity and grouping.
"What if we removed path compression from Find? 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 helps keep track of which elements belong to the same group or set.Step 2: Identify its main operation
It supports merging two groups and checking if two elements are in the same group efficiently.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
- Mixing it up with queue or stack data structures
Solution
Step 1: Understand Go slice creation
To create a slice of integers with length 5, use make([]int, 5).Step 2: Check each option
parent := make([]int, 5) correctly uses make with []int and size 5. parent := [5]int{0} is an array but initializes only first element. parent := []int{5} creates a slice with one element 5, not length 5. parent := make(int, 5) is invalid syntax.Final Answer:
parent := make([]int, 5) -> Option AQuick Check:
Go slice with make([]int, 5) = correct [OK]
- Using make(int, 5) instead of make([]int, 5)
- Confusing array and slice syntax
- Initializing slice with single element instead of length
parent := []int{0, 1, 2, 3, 4}
func find(x int) int {
if parent[x] != x {
parent[x] = find(parent[x])
}
return parent[x]
}
func union(a, b int) {
rootA := find(a)
rootB := find(b)
if rootA != rootB {
parent[rootB] = rootA
}
}
union(0, 1)
union(1, 2)
union(3, 4)
fmt.Println(find(2), find(4))
What is the output?Solution
Step 1: Trace union operations
union(0,1) merges sets of 0 and 1, so parent[1] = 0. union(1,2) merges sets of 1 and 2, so parent[2] = 0 (since find(1) = 0). union(3,4) merges sets of 3 and 4, so parent[4] = 3.Step 2: Find roots of 2 and 4
find(2) follows parent[2] = 0, so root is 0. find(4) follows parent[4] = 3, so root is 3.Final Answer:
0 3 -> Option DQuick Check:
Roots after unions = 0 and 3 [OK]
- Not applying path compression in find
- Mixing up which root belongs to which element
- Assuming union merges by value instead of root
parent := []int{0, 1, 2, 3, 4}
func find(x int) int {
if parent[x] == x {
return x
}
return find(parent[x])
}
func union(a, b int) {
rootA := find(a)
rootB := find(b)
if rootA != rootB {
parent[rootA] = rootB
}
}
union(0, 1)
union(1, 2)
fmt.Println(find(2))
Solution
Step 1: Analyze find function
The find function recursively finds the root but does not update parent[x] to root, so path compression is missing.Step 2: Understand impact of missing path compression
Without path compression, the tree can become deep, causing slower future finds. The union logic is correct, and parent array is initialized properly.Final Answer:
Missing path compression in find function -> Option BQuick Check:
Path compression missing = bug [OK]
- Thinking union direction is always wrong
- Assuming recursion causes stack overflow here
- Ignoring path compression benefits
Solution
Step 1: Track unions step-by-step
union(0,1) merges sets {0,1}, union(2,3) merges {2,3}, union(4,5) merges {4,5}, union(1,2) merges sets containing 1 and 2, so {0,1,2,3} become one set.Step 2: Check pairs
Pair 0 and 3 are in the same set {0,1,2,3}. Pairs 1 and 5, 2 and 4, 3 and 5 are in different sets.Final Answer:
0 and 3 -> Option CQuick Check:
Unions merge sets correctly = 0 and 3 same set [OK]
- Assuming all unions merge all elements
- Mixing sets after union(1,2)
- Confusing pairs in different sets
Ready to prove it?
Pass the test and this pattern moves unknown
