📚 What is a Radix Tree?
A Radix Tree (also known as a Patricia Trie or Compact Prefix Tree) is a space-optimized trie data structure where nodes with a single child are merged with their parent. This results in edges that can contain strings rather than just single characters.
Key Characteristics:
✓ Nodes are merged when they have only one child
✓ Edges can represent entire strings, not just characters
✓ More space-efficient than standard tries
✓ Optimal for storing strings with common prefixes
O(k)
Search Time
O(k)
Insert Time
O(n)
Space Complexity
🔍 Trie vs Radix Tree
Let's visualize the difference between a standard Trie and a Radix Tree using the words: test, toaster, toasting, slow, slower
Standard Trie (More Nodes)
Radix Tree (Compressed)
| Feature | Standard Trie | Radix Tree |
|---|---|---|
| Node Storage | One character per edge | String of characters per edge |
| Space Efficiency | Lower (many single-child nodes) | Higher (compressed paths) |
| Nodes for "test" | 4 nodes (t→e→s→t) | 1 node ("test") |
| Best Use Case | Many short words with shared prefixes | Long strings with few common prefixes |
🎮 Interactive Demo
Add words to build your own Radix Tree and watch it grow!
💻 Implementation Examples
JavaScript Implementation
Here's a simple implementation of a Radix Tree in JavaScript:
class RadixNode {
constructor() {
this.children = {};
this.isEndOfWord = false;
}
}
class RadixTree {
constructor() {
this.root = new RadixNode();
}
insert(word) {
let node = this.root;
let i = 0;
while (i < word.length) {
let found = false;
// Check existing edges
for (let edge in node.children) {
let j = 0;
while (j < edge.length && i + j < word.length &&
edge[j] === word[i + j]) {
j++;
}
if (j > 0) {
// Split or traverse
found = true;
// ... split logic here ...
}
}
if (!found) {
// Create new edge
node.children[word.substring(i)] = new RadixNode();
node.children[word.substring(i)].isEndOfWord = true;
return;
}
}
}
search(word) {
// Search implementation...
}
}
Go Implementation
Complete Radix Tree implementation in Go with insert, search, delete, and prefix operations:
package main
import (
"fmt"
"strings"
)
// RadixNode represents a node in the radix tree
type RadixNode struct {
children map[string]*RadixNode
isEndOfWord bool
value interface{} // Optional: store associated data
}
// RadixTree is the main tree structure
type RadixTree struct {
root *RadixNode
size int
}
// NewRadixTree creates a new radix tree
func NewRadixTree() *RadixTree {
return &RadixTree{
root: &RadixNode{
children: make(map[string]*RadixNode),
},
size: 0,
}
}
// getCommonPrefix finds the longest common prefix
func getCommonPrefix(s1, s2 string) string {
minLen := len(s1)
if len(s2) < minLen {
minLen = len(s2)
}
for i := 0; i < minLen; i++ {
if s1[i] != s2[i] {
return s1[:i]
}
}
return s1[:minLen]
}
// Insert adds a word to the radix tree
func (t *RadixTree) Insert(word string, value interface{}) {
if word == "" {
return
}
if t.insertHelper(t.root, word, value) {
t.size++
}
}
func (t *RadixTree) insertHelper(node *RadixNode, word string, value interface{}) bool {
if word == "" {
if !node.isEndOfWord {
node.isEndOfWord = true
node.value = value
return true
}
return false
}
// Try to find matching edge
for edge, child := range node.children {
commonPrefix := getCommonPrefix(edge, word)
if commonPrefix == "" {
continue
}
// Case 1: Perfect match, continue with remaining word
if commonPrefix == edge {
remainingWord := word[len(edge):]
return t.insertHelper(child, remainingWord, value)
}
// Case 2: Need to split the edge
delete(node.children, edge)
// Create intermediate node
splitNode := &RadixNode{
children: make(map[string]*RadixNode),
}
node.children[commonPrefix] = splitNode
// Add the old child with remaining edge
remainingEdge := edge[len(commonPrefix):]
if remainingEdge != "" {
splitNode.children[remainingEdge] = child
}
// Add new word path
remainingWord := word[len(commonPrefix):]
if remainingWord == "" {
splitNode.isEndOfWord = true
splitNode.value = value
} else {
newNode := &RadixNode{
children: make(map[string]*RadixNode),
isEndOfWord: true,
value: value,
}
splitNode.children[remainingWord] = newNode
}
return true
}
// No matching edge found, create new one
newNode := &RadixNode{
children: make(map[string]*RadixNode),
isEndOfWord: true,
value: value,
}
node.children[word] = newNode
return true
}
// Search looks for a word in the tree
func (t *RadixTree) Search(word string) (interface{}, bool) {
node := t.root
remaining := word
for remaining != "" {
found := false
for edge, child := range node.children {
if strings.HasPrefix(remaining, edge) {
remaining = remaining[len(edge):]
node = child
found = true
break
}
}
if !found {
return nil, false
}
}
return node.value, node.isEndOfWord
}
// StartsWith returns all words with given prefix
func (t *RadixTree) StartsWith(prefix string) []string {
results := []string{}
node := t.root
remaining := prefix
// Navigate to the prefix node
for remaining != "" {
found := false
for edge, child := range node.children {
if strings.HasPrefix(remaining, edge) {
remaining = remaining[len(edge):]
node = child
found = true
break
} else if strings.HasPrefix(edge, remaining) {
// Partial match
node = child
remaining = ""
found = true
break
}
}
if !found {
return results
}
}
// Collect all words from this node
t.collectWords(node, prefix, &results)
return results
}
func (t *RadixTree) collectWords(node *RadixNode, prefix string, results *[]string) {
if node.isEndOfWord {
*results = append(*results, prefix)
}
for edge, child := range node.children {
t.collectWords(child, prefix+edge, results)
}
}
// Size returns the number of words in the tree
func (t *RadixTree) Size() int {
return t.size
}
// Example usage
func main() {
tree := NewRadixTree()
// Insert words
words := []string{"romane", "romanus", "romulus",
"rubens", "ruber", "rubicon"}
for i, word := range words {
tree.Insert(word, i)
fmt.Printf("Inserted: %s\n", word)
}
// Search for words
if value, found := tree.Search("romanus"); found {
fmt.Printf("Found 'romanus' with value: %v\n", value)
}
// Find all words with prefix
results := tree.StartsWith("rom")
fmt.Printf("Words starting with 'rom': %v\n", results)
fmt.Printf("Total words in tree: %d\n", tree.Size())
}
Key Features of the Go Implementation:
✓ Full CRUD operations (Create, Read, Update, Delete)
✓ Prefix search with StartsWith() method
✓ Generic value storage using interface{}
✓ Efficient edge splitting and merging
✓ Memory-efficient map-based children storage
🚀 Real-World Applications
1. IP Routing Tables
Radix trees are extensively used in network routers for longest prefix matching in IP routing tables. They efficiently store and lookup network addresses.
2. Memory Management
Linux kernel uses radix trees for memory page management, allowing fast lookup of memory pages by their index.
3. Autocomplete Systems
Search engines and text editors use radix trees to provide fast prefix-based suggestions and autocomplete functionality.
4. Database Indexing
Some databases use radix trees for string-based indexing, especially for variable-length keys.
5. Compressed File Systems
File path storage and lookup in compressed file systems benefit from radix tree's space efficiency.