BST Search Operation in DSA Rust - Time & Space Complexity
We want to understand how long it takes to find a value in a Binary Search Tree (BST).
How does the search time grow as the tree gets bigger?
Analyze the time complexity of the following code snippet.
fn bst_search(root: &Option>, target: i32) -> bool {
match root {
None => false,
Some(node) => {
if target == node.value {
true
} else if target < node.value {
bst_search(&node.left, target)
} else {
bst_search(&node.right, target)
}
}
}
}
This code searches for a target value by moving left or right in the BST until it finds the value or reaches the end.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive calls moving down one level of the tree.
- How many times: At most once per tree level, until the value is found or no more nodes.
Each step moves down one level, so the number of steps depends on the tree height.
| Input Size (n) | Approx. Operations (steps) |
|---|---|
| 10 | About 4 steps (height ~ log₂(10)) |
| 100 | About 7 steps (height ~ log₂(100)) |
| 1000 | About 10 steps (height ~ log₂(1000)) |
Pattern observation: Steps grow slowly, roughly doubling input size adds only one extra step.
Time Complexity: O(h) where h is the tree height
This means the search time depends on how tall the tree is, not just how many nodes it has.
[X] Wrong: "Searching a BST always takes the same time as the number of nodes (O(n))."
[OK] Correct: If the tree is balanced, search only goes down the height, which is much smaller than total nodes.
Knowing how BST search time depends on tree height helps you explain why balanced trees are important and shows you understand efficient searching.
"What if the BST becomes a linked list (all nodes only have one child)? How would the time complexity change?"
Practice
Solution
Step 1: Understand the purpose of BST search
The search operation checks if a specific value is present in the BST by comparing values and moving left or right accordingly.Step 2: Differentiate from other BST operations
Insertion adds new nodes, deletion removes nodes, and counting nodes is a separate operation. Search only checks presence.Final Answer:
Finds whether a given value exists in the BST -> Option DQuick Check:
Search = Find value [OK]
- Confusing search with insert or delete
- Thinking search counts nodes
- Assuming search modifies the tree
Solution
Step 1: Identify method receiver and return type
Search should borrow the tree immutably (&self) and return true/false (bool) indicating presence.Step 2: Check parameters and return correctness
Value parameter is i32, and return type is bool. Mutable borrow or returning Node is incorrect for simple search.Final Answer:
fn search(&self, value: i32) -> bool -> Option CQuick Check:
Immutable borrow + bool return = fn search(&self, value: i32) -> bool [OK]
- Using &mut self instead of &self
- Returning Node instead of bool
- Missing self parameter
struct Node {
value: i32,
left: Option,
right: Option,
}
impl Node {
fn search(&self, val: i32) -> bool {
if val == self.value {
true
} else if val < self.value {
match &self.left {
Some(left_node) => left_node.search(val),
None => false,
}
} else {
match &self.right {
Some(right_node) => right_node.search(val),
None => false,
}
}
}
}
fn main() {
let root = Node {
value: 5,
left: Some(Box::new(Node { value: 3, left: None, right: None })),
right: Some(Box::new(Node { value: 8, left: None, right: None })),
};
println!("{}", root.search(7));
}Solution
Step 1: Trace search for value 7 starting at root 5
7 > 5, so search right subtree with root 8.Step 2: Search right subtree node with value 8 for 7
7 < 8, so search left subtree of node 8, which is None, so returns false.Final Answer:
false -> Option AQuick Check:
7 not found in tree = false [OK]
- Assuming 7 is found because 8 exists
- Confusing left and right subtree directions
- Ignoring None case returns false
fn search(&self, val: i32) -> bool {
if val == self.value {
true
} else if val < self.value {
self.left.as_ref().unwrap().search(val)
} else {
self.right.as_ref().unwrap().search(val)
}
}Solution
Step 1: Analyze use of unwrap() on Option
unwrap() assumes the Option is Some, but if child is None, it causes runtime panic.Step 2: Understand safe alternative
Should check if child exists using match or if let before calling search to avoid panic.Final Answer:
Using unwrap() without checking if child exists can cause panic -> Option AQuick Check:
unwrap() on None = panic [OK]
- Ignoring possibility of None causing panic
- Thinking unwrap() is safe always
- Confusing return types
Solution
Step 1: Understand duplicates on right subtree
Duplicates are stored on the right, so if val equals node value, we found it; else if val > node, search right subtree.Step 2: Modify search to check current node and right subtree for duplicates
If val equals node value, return true immediately. If val > node value, search right subtree. If val < node value, search left subtree.Final Answer:
Search left subtree if val < node, else search both right subtree and current node for val -> Option BQuick Check:
Duplicates on right: check current and right subtree [OK]
- Ignoring duplicates on right subtree
- Searching only one subtree without checking current node
- Searching left subtree unnecessarily for duplicates
Ready to prove it?
Pass the test and this pattern moves unknown
