Collision Handling Using Open Addressing Linear Probing in DSA Rust - Time & Space Complexity
We want to understand how long it takes to insert or find items when collisions happen in a hash table using linear probing.
How does the time grow as the table fills up?
Analyze the time complexity of the following code snippet.
fn linear_probing_insert(table: &mut [Option], key: i32) {
let size = table.len();
let mut index = (key as usize) % size;
while table[index].is_some() {
index = (index + 1) % size;
}
table[index] = Some(key);
}
This code inserts a key into a hash table using linear probing to find the next free spot when a collision occurs.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The while loop that checks each slot until an empty one is found.
- How many times: In the worst case, it can check almost every slot in the table.
When the table is mostly empty, the loop runs very few times. As the table fills, the loop runs more times, sometimes nearly the whole table.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 (mostly empty) | 1-2 checks |
| 100 (half full) | 5-10 checks |
| 1000 (almost full) | up to 1000 checks |
Pattern observation: The more full the table, the longer the search for an empty slot, growing roughly linearly with table size.
Time Complexity: O(n)
This means in the worst case, the time to insert or find a key grows linearly with the size of the table.
[X] Wrong: "Insertion always takes constant time because we just compute the hash once."
[OK] Correct: When collisions happen, we may need to check many slots, so the time depends on how full the table is.
Understanding how collision handling affects performance shows you know practical trade-offs in data structures, a key skill in coding interviews.
"What if we changed linear probing to quadratic probing? How would the time complexity change?"
Practice
linear probing in collision handling for hash tables?Solution
Step 1: Understand collision handling methods
Linear probing is a method where, upon collision, the algorithm checks the next slot in the array sequentially.Step 2: Compare with other methods
Other methods include chaining (linked lists), resizing, or double hashing, but linear probing specifically uses sequential slots.Final Answer:
If a collision occurs, check the next slot sequentially until an empty slot is found. -> Option DQuick Check:
Linear probing = sequential slot check [OK]
- Confusing linear probing with chaining
- Thinking linear probing uses a second hash function
- Assuming table size doubles on collision
table_size?Solution
Step 1: Understand linear probing index calculation
Linear probing moves to the next slot by adding 1 to the current index and wrapping around using modulo.Step 2: Check each option
let next_index = (current_index + 1) % table_size; adds 1 and uses modulo, which is correct. Others multiply or subtract, which are not standard linear probing steps.Final Answer:
let next_index = (current_index + 1) % table_size; -> Option AQuick Check:
Linear probing = increment index by 1 modulo table size [OK]
- Using multiplication instead of addition
- Subtracting index without handling negative wrap
- Skipping modulo operation causing out-of-bounds
key % 10?
let mut table = vec![None; 10];
let keys = vec![5, 15, 25];
for &key in &keys {
let mut index = key % 10;
while table[index].is_some() {
index = (index + 1) % 10;
}
table[index] = Some(key);
}
println!("{:?}", table);Solution
Step 1: Calculate initial indices for keys
Keys 5, 15, 25 all hash to index 5 (5 % 10 = 5, 15 % 10 = 5, 25 % 10 = 5).Step 2: Insert keys using linear probing
Insert 5 at index 5. For 15, index 5 is occupied, so move to 6. Insert 15 at 6. For 25, indices 5 and 6 are occupied, move to 7 and insert 25.Final Answer:
[None, None, None, None, None, Some(5), Some(15), Some(25), None, None] -> Option CQuick Check:
Linear probing places keys at 5, 6, 7 [OK]
- Placing all keys at index 5 ignoring collisions
- Skipping modulo wrap-around
- Inserting keys at wrong indices after collision
let mut table = vec![None; 5];
let keys = vec![1, 6, 11];
for &key in &keys {
let mut index = key % 5;
while table[index].is_some() {
index = (index + 1) % 5;
}
table[index] = Some(key);
}
Solution
Step 1: Analyze the while loop condition
The loop continues while the slot is occupied, moving to next index with wrap-around.Step 2: Identify missing termination condition
If the table is full, the loop cycles endlessly because no empty slot is found and no exit condition exists.Final Answer:
The code does not check if the table is full, causing infinite loop when no empty slot exists. -> Option AQuick Check:
Missing full-table check causes infinite loop [OK]
- Ignoring full table condition
- Changing modulo divisor incorrectly
- Breaking loop too early causing wrong insertion
key % 7. After all insertions, what is the state of the table? Represent empty slots as null.
Explain your answer step-by-step.Solution
Step 1: Calculate initial indices for each key
10 % 7 = 3, 20 % 7 = 6, 15 % 7 = 1, 7 % 7 = 0, 32 % 7 = 4.Step 2: Insert keys using linear probing
All hashed indices are empty, so place: 10 at 3, 20 at 6, 15 at 1, 7 at 0, 32 at 4.Step 3: Final table state
Indices 2 and 5 remain null: [7, 15, null, 10, 32, null, 20].Final Answer:
[7, 15, null, 10, 32, null, 20] -> Option BQuick Check:
Insert keys at hashed index or next free slot [OK]
- Not applying modulo correctly
- Placing keys at wrong indices after collision
- Ignoring empty slots in final output
Ready to prove it?
Pass the test and this pattern moves unknown
