7.10 Ordered Maps
Arrays are excellent when data rarely changes.
7.10 Ordered Maps
Arrays are excellent when data rarely changes. Binary search provides fast lookups, but insertion and deletion are expensive because elements must be shifted to preserve ordering.
Many real systems require both efficient searches and efficient updates. Databases insert records continuously. Schedulers add and remove tasks. Caches expire entries. Financial systems process streams of orders. In these environments, rebuilding or resorting an array after every modification is impractical.
Ordered maps solve this problem by maintaining key-value pairs in sorted order while supporting efficient insertion, deletion, and lookup operations.
Unlike hash tables, which prioritize average-case lookup speed, ordered maps preserve ordering relationships between keys. This additional structure enables range queries, predecessor searches, successor searches, ordered iteration, and interval operations.
Most balanced search trees exist primarily to implement ordered maps.
Problem
Suppose you need to store user information indexed by user ID.
A simple array-based approach might look like:
type User struct {
ID int
Name string
}
Store users in a sorted slice:
users := []User{
{100, "Alice"},
{200, "Bob"},
{300, "Charlie"},
}
Lookup can use binary search.
func FindUser(
users []User,
id int,
) int {
lo, hi := 0, len(users)
for lo < hi {
mid := lo + (hi-lo)/2
if users[mid].ID >= id {
hi = mid
} else {
lo = mid + 1
}
}
return lo
}
Searching is efficient:
O(log n)
Insertion is not.
Adding:
150
requires:
[100, 200, 300]
to become:
[100, 150, 200, 300]
which may require moving many elements.
Insertion cost:
O(n)
This becomes problematic for large collections.
Solution
Use an ordered map implemented with a balanced search tree.
Conceptually:
200
/ \\n 100 300
Each node stores:
- A key
- A value
- References to child nodes
Search follows the ordering relationship.
Target = 300
300 > 200
Move right
300 == 300
Found
Only a logarithmic number of nodes are visited.
Operations become:
| Operation | Complexity |
|---|---|
| Lookup | O(log n) |
| Insert | O(log n) |
| Delete | O(log n) |
This combination makes ordered maps one of the most important data structures in computer science.
The Ordered Map Abstraction
Conceptually, an ordered map provides:
type OrderedMap interface {
Put(key, value)
Get(key)
Delete(key)
}
Unlike a hash map, keys remain sorted.
Example:
100 -> Alice
200 -> Bob
300 -> Charlie
Iterating over the map produces:
100
200
300
in key order.
This property enables operations that are impossible or expensive with hash tables.
Lookup
Search begins at the root.
Example:
50
/ \\n 25 75
/ \ / \\n 10 40 60 90
Search for:
60
Path:
50 -> 75 -> 60
Only three nodes are visited.
The search space is reduced at each step in the same way binary search reduces an array.
This similarity is not accidental. Balanced trees are essentially binary search generalized to dynamic collections.
Insertion
Suppose:
50
/ \\n 25 75
Insert:
60
Search path:
50 -> 75
Since:
60 < 75
insert as the left child.
Result:
50
/ \\n 25 75
/
60
The insertion location is found in logarithmic time.
The challenge is maintaining balance after many insertions.
Later sections address this problem using self-balancing trees.
Deletion
Deletion is more complicated.
Three cases exist:
Leaf node
50
/
25
Delete:
25
Simply remove the node.
One child
50
/
25
\\n 40
Delete:
25
Promote the child.
50
/
40
Two children
50
/ \\n 25 75
Delete:
50
Replace with predecessor or successor.
The exact details depend on the tree implementation.
Balanced tree structures automate this process while preserving ordering properties.
Why Not Use Hash Maps?
Hash maps provide:
Average lookup: O(1)
which appears superior.
However, consider:
Find all keys between
1000 and 5000
A hash map has no notion of order.
The only solution is scanning every entry.
O(n)
An ordered map can locate:
LowerBound(1000)
and iterate until:
5000
The cost becomes:
O(log n + k)
where:
k
is the number of returned records.
For range queries, ordered maps are often dramatically faster.
Predecessor and Successor Searches
Given:
100
200
300
400
500
Find the largest value less than:
350
Result:
300
Find the smallest value greater than:
350
Result:
400
These operations are known as:
Predecessor
Successor
Ordered maps support them naturally.
Hash maps do not.
This capability is important in scheduling, event processing, memory allocation, and database indexing.
Range Queries
Suppose a time-series system stores:
09:00
09:15
09:30
09:45
10:00
10:15
Query:
09:20 - 10:00
An ordered map can quickly locate:
09:30
and iterate forward until:
10:00
No unrelated records are examined.
This is one of the primary reasons databases rely heavily on ordered indexes.
Ordered Maps in Practice
Many systems expose ordered maps under different names.
Examples include:
- TreeMap (Java)
- std::map (C++)
- BTreeMap (Rust)
- SortedDictionary (.NET)
- B-tree indexes in databases
- Filesystem directory indexes
- Key-value storage engines
Although implementations differ, the abstraction remains similar.
Each structure maintains ordered keys while supporting efficient updates.
Arrays Versus Ordered Maps
| Feature | Sorted Array | Ordered Map |
|---|---|---|
| Lookup | O(log n) | O(log n) |
| Insert | O(n) | O(log n) |
| Delete | O(n) | O(log n) |
| Ordered iteration | Yes | Yes |
| Range queries | Yes | Yes |
| Cache locality | Excellent | Moderate |
| Dynamic updates | Poor | Excellent |
Arrays often outperform trees for read-heavy workloads because of superior cache behavior.
Ordered maps excel when updates occur frequently.
Choosing between them depends on workload characteristics.
Database Perspective
Consider a table indexed by customer ID.
1001
1005
1012
1019
1024
1033
A query asks:
SELECT *
FROM customers
WHERE customer_id
BETWEEN 1010 AND 1025
The index performs:
LowerBound(1010)
and then scans forward until:
1025
This is fundamentally an ordered-map operation.
Most database indexing techniques are specialized forms of ordered maps optimized for disk and memory hierarchies.
Complexity Analysis
Assuming the structure remains balanced:
| Operation | Complexity |
|---|---|
| Lookup | O(log n) |
| Insert | O(log n) |
| Delete | O(log n) |
| Lower bound | O(log n) |
| Upper bound | O(log n) |
| Range query | O(log n + k) |
where:
k
is the number of returned elements.
Balance is essential. Without it, performance can degrade significantly.
Common Mistakes
A common misconception is that hash maps are always superior because of constant-time lookups.
Hash maps optimize exact lookups. Ordered maps optimize ordered operations.
Another mistake is ignoring workload characteristics. If updates are rare, a sorted array may outperform an ordered map because of better memory locality.
A third mistake is assuming balanced trees maintain themselves automatically. Balancing algorithms are responsible for preserving logarithmic performance. Without balancing, an ordered map can degenerate into a linked list.
Takeaway
Ordered maps combine the ordering benefits of sorted arrays with the update efficiency of balanced trees. They support logarithmic-time lookup, insertion, deletion, predecessor searches, successor searches, and range queries while preserving key order. Many higher-level data structures, database indexes, schedulers, storage engines, and search systems are ultimately specialized ordered maps designed for particular performance and storage requirements. In the following sections, we will examine the balanced tree structures that make these guarantees possible.