Path Compression in Union Find in DSA Rust - Time & Space Complexity
We want to understand how fast the "find" operation runs when we use path compression in Union Find.
How does the time to find the root change as we do more operations?
Analyze the time complexity of the following code snippet.
fn find(parent: &mut Vec<usize>, x: usize) -> usize {
if parent[x] != x {
parent[x] = find(parent, parent[x]);
}
return parent[x];
}
This code finds the root of element x and compresses the path by making every node point directly to the root.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive calls to find until reaching the root.
- How many times: At most once per node on the path from x to root, but path compression shortens future calls.
Initially, paths can be long, so find may take several steps. But after many finds, paths become short, making future finds faster.
| Input Size (n) | Approx. Operations per find |
|---|---|
| 10 | Up to 4-5 steps initially, then close to 1 step |
| 100 | Up to 10-15 steps initially, then close to 1 step |
| 1000 | Up to 20-30 steps initially, then close to 1 step |
Pattern observation: The cost per find decreases over time, approaching almost constant time.
Time Complexity: O(α(n))
This means each find operation runs in almost constant time, where α(n) is a very slowly growing function.
[X] Wrong: "Each find always takes linear time because it may traverse many nodes."
[OK] Correct: Path compression flattens the tree, so after some finds, the path is very short, making future finds much faster.
Understanding path compression's time complexity shows you can analyze how clever tricks improve performance beyond simple loops.
This skill helps you explain why some data structures are efficient in practice, a valuable insight in interviews.
"What if we removed path compression? How would the time complexity of find change then?"
Practice
path compression in the Union Find data structure?Solution
Step 1: Understand the role of path compression
Path compression is used during thefindoperation to make the tree flatter by pointing nodes directly to the root.Step 2: Effect on future operations
This flattening reduces the time needed for futurefindcalls, making them faster.Final Answer:
To speed up futurefindoperations by flattening the tree -> Option BQuick Check:
Path compression = fasterfind[OK]
- Thinking path compression merges sets
- Confusing path compression with union operation
- Believing it deletes elements
find method with path compression in a Union Find struct?Solution
Step 1: Identify mutability requirement
Path compression modifies the parent array, soselfmust be mutable reference&mut self.Step 2: Check return type and parameters
The method takes an element index and returns its root asusize.Final Answer:
fn find(&mut self, x: usize) -> usize -> Option AQuick Check:
Mutable self needed for path compression [OK]
- Using &self which is immutable
- Taking ownership of self
- Missing self parameter
uf.find(3)?
struct UnionFind {
parent: Vec<usize>,
}
impl UnionFind {
fn find(&mut self, x: usize) -> usize {
if self.parent[x] != x {
self.parent[x] = self.find(self.parent[x]);
}
self.parent[x]
}
}
fn main() {
let mut uf = UnionFind { parent: vec![0, 0, 1, 2] };
let root = uf.find(3);
println!("{}", root);
}Solution
Step 1: Trace the parent array before find
Initial parent: [0, 0, 1, 2]. Element 3 points to 2, 2 points to 1, 1 points to 0.Step 2: Perform find(3) with path compression
find(3) calls find(2), which calls find(1), which calls find(0). Since 0 is its own parent, root is 0. Path compression sets parent[3] = 0 and parent[2] = 0.Final Answer:
0 -> Option DQuick Check:
Root of 3 after compression = 0 [OK]
- Stopping at immediate parent without recursion
- Not updating parent during compression
- Confusing indices with values
find method with path compression and select the correct fix:
fn find(&mut self, x: usize) -> usize {
if self.parent[x] == x {
self.parent[x] = self.find(self.parent[x]);
}
self.parent[x]
}Solution
Step 1: Analyze the if condition logic
The conditionself.parent[x] == xmeans x is root, so no recursion needed. The code wrongly recurses when x is root.Step 2: Correct the condition
It should recurse only if x is not root, so condition must beself.parent[x] != x.Final Answer:
Change == to != in the if condition -> Option AQuick Check:
Recurse only if not root [OK]
- Recursing when node is root
- Forgetting base case
- Misusing equality in condition
parent = [0,1,2,3,4]. After performing unions union(0,1), union(1,2), and union(3,4), what will be the parent array after calling find(2) with path compression if unions always attach the second root to the first root?Solution
Step 1: Perform unions step-by-step
union(0,1): parent[1] = 0 -> [0,0,2,3,4] union(1,2): find(1)=0, find(2)=2, parent[2] = 0 -> [0,0,0,3,4] union(3,4): parent[4] = 3 -> [0,0,0,3,3]Step 2: Call find(2) with path compression
find(2) sees parent[2]=0 which is root, no change. Parents remain [0,0,0,3,3].Final Answer:
[0, 0, 0, 3, 3] -> Option CQuick Check:
Unions attach second root to first, path compression confirms roots [OK]
- Not updating parents during union
- Confusing which root attaches to which
- Ignoring path compression effect
Ready to prove it?
Pass the test and this pattern moves unknown
