Linear Probing Search
Resolve hash collisions by scanning sequential slots until the key is found or an empty slot appears.
Linear Probing Search
Linear probing resolves collisions in an open addressing hash table by scanning forward one slot at a time. All keys are stored directly inside the table, and probing follows a contiguous sequence.
You search by computing the hash index and walking forward until you either find the key or reach an empty slot.
Problem
Given a hash table using linear probing and a key $k$, return the associated value if it exists.
Model
- Table $T$ has size $m$
- Hash function $h(k)$
- Probe sequence:
$$ i_t = (h(k) + t) \bmod m $$
for $t = 0, 1, 2, \dots$
Algorithm
linear_probing_search(T, k):
i = h(k)
start = i
while T[i] is occupied:
if T[i].key == k:
return T[i].value
i = (i + 1) mod m
if i == start:
break
return NOT_FOUND
Example
Let:
- $m = 7$
- $h(k) = k \bmod 7$
Insert:
$$ [10, 17, 24] $$
All hash to index $3$, so they occupy:
| index | value |
|---|---|
| 3 | 10 |
| 4 | 17 |
| 5 | 24 |
Search for $k = 24$:
- start at index 3
- check 10, then 17, then 24
- match found
Correctness
Linear probing guarantees that every key is placed somewhere along its probe sequence. The lookup follows exactly the same sequence, so it will encounter the key if it exists.
An empty slot terminates the search, proving that the key does not exist.
Complexity
Let $\alpha = n/m$ be the load factor.
| case | time |
|---|---|
| average | $O(1)$ |
| worst | $O(n)$ |
Performance degrades as $\alpha$ approaches 1 due to clustering.
Space
$$ O(m) $$
All elements are stored directly in the table.
When to Use
Linear probing is appropriate when:
- memory locality is important
- cache performance matters
- implementation simplicity is desired
It performs poorly under high load due to primary clustering.
Implementation
class HashTable:
def __init__(self, m=8):
self.m = m
self.table = [None] * m
def _hash(self, key):
return hash(key) % self.m
def get(self, key):
i = self._hash(key)
start = i
while self.table[i] is not None:
k, v = self.table[i]
if k == key:
return v
i = (i + 1) % self.m
if i == start:
break
return None
type Entry[K comparable, V any] struct {
Key K
Value V
Used bool
}
type HashTable[K comparable, V any] struct {
Table []Entry[K, V]
M int
}
func (h *HashTable[K, V]) hash(key K) int {
return int(uintptr(any(key))) % h.M
}
func (h *HashTable[K, V]) Get(key K) (V, bool) {
i := h.hash(key)
start := i
for h.Table[i].Used {
if h.Table[i].Key == key {
return h.Table[i].Value, true
}
i = (i + 1) % h.M
if i == start {
break
}
}
var zero V
return zero, false
}