Search for a string or byte key in a radix tree using variable length edge labels.
Radix tree search looks up a key in a compact prefix tree where edges store variable length substrings.
It is closely related to compressed tries. The main difference is that radix trees are typically implemented for byte sequences or strings and optimize memory layout and lookup performance.
Problem
Given a radix tree root and a key s, determine whether s exists as a stored key.
Return true if present, otherwise false.
Structure
Each node stores outgoing edges indexed by the first symbol of their label. Each edge contains:
| field | meaning |
|---|---|
label | substring |
child | next node |
Nodes also store a terminal flag indicating complete keys.
Algorithm
Start at the root and consume the input string by matching edge labels.
radix_tree_search(root, s):
node = root
i = 0
while i < length(s):
c = s[i]
if c not in node.edges:
return false
edge = node.edges[c]
label = edge.label
if s[i : i + length(label)] != label:
return false
i = i + length(label)
node = edge.child
return node.is_terminalExample
Stored keys:
| key |
|---|
| “test” |
| “team” |
| “tea” |
Possible radix tree:
(root)
└─ "te"
├─ "st" (terminal)
└─ "a" (terminal)
└─ "m" (terminal)Search for "team":
| step | remaining | edge |
|---|---|---|
| 1 | team | te |
| 2 | am | a |
| 3 | m | m |
The final node is terminal, so the key exists.
Search for "ten" fails after matching "te" because no edge labeled "n" exists.
Correctness
Each edge represents a sequence of characters that would appear consecutively in an uncompressed trie. Matching an edge label enforces exact agreement with stored prefixes.
If no matching edge exists for the next character, no stored key shares that prefix. If an edge label does not match the substring, the search diverges from all stored keys.
After consuming the full input, the terminal flag distinguishes a stored key from a prefix. Therefore the algorithm returns true exactly when the key exists.
Complexity
Let be the length of the key.
| operation | time |
|---|---|
| search |
The number of node transitions is typically smaller than in a standard trie because multiple characters are consumed per edge.
Space complexity:
for iterative search.
When to Use
Radix trees are useful when:
- keys are strings or byte sequences
- memory usage must be reduced compared to standard tries
- prefix queries and ordered traversal are required
- performance benefits from fewer node hops
They are commonly used in routing tables, dictionaries, and indexing systems.
Implementation
class RadixEdge:
def __init__(self, label, child):
self.label = label
self.child = child
class RadixNode:
def __init__(self):
self.edges = {}
self.is_terminal = False
def radix_tree_search(root, s):
node = root
i = 0
while i < len(s):
c = s[i]
if c not in node.edges:
return False
edge = node.edges[c]
label = edge.label
if not s.startswith(label, i):
return False
i += len(label)
node = edge.child
return node.is_terminaltype RadixEdge struct {
Label string
Child *RadixNode
}
type RadixNode struct {
Edges map[rune]*RadixEdge
IsTerminal bool
}
func RadixTreeSearch(root *RadixNode, s string) bool {
node := root
runes := []rune(s)
i := 0
for i < len(runes) {
c := runes[i]
edge, ok := node.Edges[c]
if !ok {
return false
}
label := []rune(edge.Label)
if i+len(label) > len(runes) {
return false
}
for j := range label {
if runes[i+j] != label[j] {
return false
}
}
i += len(label)
node = edge.Child
}
return node.IsTerminal
}