Hashing - DSA | Chapter: Open Addressing II: Quadratic Probing Mechanics
This chapter explores the mechanics of Quadratic Probing, an advanced collision resolution technique in open-addressed hash tables. We analyze the limitations of Linear Probing—specifically the performance-degrading phenomenon known as Primary Clustering—and demonstrate how Quadratic Probing resolves this by using non-linear, quadratic step sizes to disperse keys uniformly across the hash table.
1. The Problem with Linear Probing: Primary Clustering
In Linear Probing, when a collision occurs at a hash index, we search for the next available slot sequentially (i.e., index by index: ). While simple to implement, this sequential search strategy introduces a major performance bottleneck known as Primary Clustering.
What is Primary Clustering?
Primary Clustering is the tendency for occupied slots in a hash table to form continuous, dense blocks (clusters) over time. Once a cluster forms, the probability of subsequent keys hashing into or near that cluster increases. This causes a cascading effect where collisions become increasingly frequent, and resolving them requires longer search paths.
The Cost of Primary Clustering
When a large cluster forms, inserting a new element that hashes into the cluster requires traversing the entire block of occupied slots before finding an empty space.
- Time Complexity Degradation: The average-case time complexity of insertion and search operations degrades from toward (where is the number of elements in the table).
- Inefficient Traversals: To insert a single element, the system must perform multiple sequential lookups, wasting CPU cycles and destroying cache locality benefits due to prolonged search paths.
2. Real-Life Analogy: The Parking Lot Bottleneck
💡 Real-Life Analogy
Imagine a popular shopping mall parking lot with numbered spaces from 0 to 9.
Under a Linear Probing strategy, drivers are instructed: "If your preferred spot is taken, check the very next spot, then the next, and so on." If spots 4, 5, 6, and 7 are occupied, a new driver wanting spot 4 must slowly drive past spots 5, 6, and 7 before finally parking in spot 8. This creates a massive traffic bottleneck (a cluster) around that specific section of the parking lot.
Under a Quadratic Probing strategy, the instruction changes: "If your preferred spot is taken, don't crawl space-by-space. Instead, jump ahead by (1 spot), then (4 spots), then (9 spots)." This disperses the cars across different sections of the parking lot, preventing traffic jams and distributing the cars evenly.
3. Introduction to Quadratic Probing
To eliminate the primary clustering bottleneck, we transition from linear search steps to quadratic search steps. Instead of checking the immediate next slot, we use a quadratic function of the collision iteration variable to determine the next probe position.
The Core Mental Model
If our initial hash index is occupied, we probe the table at progressively larger intervals:
- Probe 0 (): Base Hash Index
- Probe 1 (): Base Hash Index
- Probe 2 (): Base Hash Index (4 slots away)
- Probe 3 (): Base Hash Index (9 slots away)
By squaring the step index (), we "jump" over potential clusters, ensuring that keys hashing to nearby locations do not merge into a single continuous block of occupied slots.
4. Mathematical Formulation & Hash Function
The hash function for Quadratic Probing is defined mathematically as:
Where:
- : The key being inserted or searched.
- : The probe number (collision iteration index), starting at .
- : The auxiliary/base hash function (typically ).
- : The size of the hash table.
- : The resolved slot index in the hash table.
Step-by-Step Probe Sequence Generation
For any key , the sequence of indices probed will be:
i = 0: Index_0 = ( h'(k) + 0 ) % m
i = 1: Index_1 = ( h'(k) + 1 ) % m
i = 2: Index_2 = ( h'(k) + 4 ) % m
i = 3: Index_3 = ( h'(k) + 9 ) % m
i = 4: Index_4 = ( h'(k) + 16 ) % m
5. Step-by-Step Execution Walkthrough
Let us trace the insertion of a sequence of keys into a hash table of size (indices to ) using Quadratic Probing.
Base Hash Function:
Keys to Insert:
Step 1: Insert
- :
- Status: Slot 2 is empty.
- Action: Insert at index 2.
Step 2: Insert
- :
- Status: Slot 4 is empty.
- Action: Insert at index 4.
Step 3: Insert
- :
- Status: Slot 6 is empty.
- Action: Insert at index 6.
Step 4: Insert (First Collision)
- :
- :
- Status: Slot 5 is empty.
- Action: Insert at index 5.
Step 5: Insert
- :
- Status: Slot 8 is empty.
- Action: Insert at index 8.
Step 6: Insert (Multiple Collisions)
- :
- :
- :
- :
- Status: Slot 3 is empty.
- Action: Insert at index 3.
Final Hash Table State
6. Visualizing the Probe Path (Key: 4)
The diagram below illustrates the non-linear, jumping behavior of Quadratic Probing when resolving collisions for key 4:
7. Tabular Synthesis: Linear vs. Quadratic Probing
8. Implementation in Code
Below is a complete, production-grade Python implementation of a Hash Table utilizing Quadratic Probing for collision resolution.
class QuadraticProbingHashTable:
def __init__(self, size: int):
self.size = size
self.table = [None] * self.size
self.keys_count = 0
def _hash(self, key: int) -> int:
return key % self.size
def insert(self, key: int) -> bool:
if self.keys_count >= self.size:
raise OverflowError("Hash Table is full")
base_index = self._hash(key)
for i in range(self.size):
# Quadratic probing formula: (base_index + i^2) % size
probe_index = (base_index + (i * i)) % self.size
# If slot is empty or contains a deleted placeholder
if self.table[probe_index] is None or self.table[probe_index] == -1:
self.table[probe_index] = key
self.keys_count += 1
print(f"Key {key} inserted at index {probe_index} (Probe {i})")
return True
print(f"Collision at index {probe_index} for key {key} (Probe {i})")
print(f"Failed to insert key {key} due to probing cycle limits.")
return False
def display(self):
for idx, val in enumerate(self.table):
print(f"Index {idx}: {val if val is not None else 'Empty'}")
# --- Execution Walkthrough ---
if __name__ == "__main__":
ht = QuadraticProbingHashTable(10)
keys = [72, 84, 96, 94, 58, 4]
for k in keys:
ht.insert(k)
print("\nFinal Hash Table State:")
ht.display()
9. Real-World Pitfalls & Edge Cases
While Quadratic Probing successfully mitigates Primary Clustering, it introduces its own set of unique engineering challenges:
1. Secondary Clustering
If two keys have the same initial hash value (), they will traverse the exact same quadratic probe sequence:
This is known as Secondary Clustering. While much less severe than Primary Clustering, it can still cause minor performance degradation.
2. The "Infinite Loop" / Unreachable Slots Problem
Unlike Linear Probing, which guarantees that every single slot in the table will be checked, Quadratic Probing can get stuck in a cyclic loop, probing the same subset of indices repeatedly while completely missing empty slots.
Note: To guarantee that the quadratic probe sequence visits at least half of the table slots before repeating, two strict conditions must be met:
- The table size must be a Prime Number.
- The Load Factor () must be kept strictly below 0.5 (less than 50% full).
Summary and Key Takeaways
- Primary Clustering is a major drawback of Linear Probing where consecutive occupied slots form large blocks, degrading search and insertion times to .
- Quadratic Probing resolves primary clustering by replacing the linear step size with a quadratic step size ().
- The mathematical formula for Quadratic Probing is .
- As the probe index increases, the search interval grows quadratically (), allowing the algorithm to jump over dense clusters.
- Secondary Clustering occurs when keys with the same initial hash value follow the same probe path, though its performance impact is significantly lower than primary clustering.
- To prevent infinite loops and ensure that empty slots are always found, the hash table size should be a prime number and the load factor should be kept under 50%.