Binary search over an array arranged in heap order to improve cache locality and branch predictability.
Eytzinger layout search reorganizes a sorted array into a breadth first tree layout, also known as heap order. The goal is to improve cache locality and make memory access patterns more predictable.
Instead of storing data in sorted order, the array is rearranged so that binary search follows a sequential memory pattern. This reduces cache misses and improves throughput on modern CPUs.
Problem
Given a sorted array and a target , find an index such that
The data is stored in Eytzinger layout rather than sorted order.
Layout
In Eytzinger layout, the array represents a complete binary tree:
- root at index
- left child at
- right child at
The values are placed so that an in order traversal yields the sorted sequence.
Example:
Sorted array:
Eytzinger layout:
| index | value |
|---|---|
| 1 | 12 |
| 2 | 6 |
| 3 | 18 |
| 4 | 3 |
| 5 | 9 |
| 6 | 15 |
| 7 | 21 |
Idea
Search proceeds like traversing a binary search tree, but the nodes are stored in a flat array. Each step moves to either the left or right child.
The key benefit is that memory access becomes more predictable, often prefetch friendly.
Algorithm
eytzinger_search(A, x):
i = 1
n = length(A) - 1 # assume 1-based array
while i <= n:
if A[i] == x:
return i
if x < A[i]:
i = 2 * i
else:
i = 2 * i + 1
return -1To support lower bound queries, track the last position where the value was greater than or equal to .
Example
Using the layout above, search for:
Traversal:
| step | index | value | action |
|---|---|---|---|
| 1 | 1 | 12 | go right |
| 2 | 3 | 18 | go left |
| 3 | 6 | 15 | found |
Correctness
The layout preserves the binary search tree invariant:
- left subtree contains smaller values
- right subtree contains larger values
Thus each comparison eliminates half of the remaining candidates, exactly like binary search. The difference lies only in memory arrangement, not logical behavior.
Complexity
| case | time |
|---|---|
| all cases |
Space:
The array is a permutation of the original data.
When to Use
Use Eytzinger layout when:
- data is static
- search dominates workload
- cache performance is critical
- branch prediction and memory access patterns matter
It is widely used in high performance search systems and database indexes.
Implementation
def build_eytzinger(sorted_array):
n = len(sorted_array)
e = [None] * (n + 1)
i = 0
def dfs(k):
nonlocal i
if k <= n:
dfs(2 * k)
e[k] = sorted_array[i]
i += 1
dfs(2 * k + 1)
dfs(1)
return e
def eytzinger_search(e, x):
i = 1
n = len(e) - 1
while i <= n:
if e[i] == x:
return i
if x < e[i]:
i = 2 * i
else:
i = 2 * i + 1
return -1func EytzingerSearch(e []int, x int) int {
i := 1
n := len(e) - 1
for i <= n {
if e[i] == x {
return i
}
if x < e[i] {
i = 2 * i
} else {
i = 2*i + 1
}
}
return -1
}