Trie (Prefix Tree)
A Trie (pronounced "try") is a tree-like data structure used to store and retrieve strings efficiently. Each path from root to a node represents a prefix.
Structure
1type TrieNode struct {
2 children map[rune]*TrieNode
3 isEnd bool // Marks end of a word
4}
5
6type Trie struct {
7 root *TrieNode
8}
9
10func NewTrie() *Trie {
11 return &Trie{
12 root: &TrieNode{
13 children: make(map[rune]*TrieNode),
14 },
15 }
16}
Visualization
Words stored: "cat", "car", "the", "their"
Core Operations
1. Insert
1func (t *Trie) Insert(word string) {
2 node := t.root
3
4 for _, char := range word {
5 if node.children[char] == nil {
6 node.children[char] = &TrieNode{
7 children: make(map[rune]*TrieNode),
8 }
9 }
10 node = node.children[char]
11 }
12
13 node.isEnd = true
14}
15
16// Example
17func main() {
18 trie := NewTrie()
19 trie.Insert("cat")
20 trie.Insert("car")
21 trie.Insert("card")
22}
Time: $O(m)$ where $m$ is word length
Space: $O(m)$
2. Search
1func (t *Trie) Search(word string) bool {
2 node := t.root
3
4 for _, char := range word {
5 if node.children[char] == nil {
6 return false
7 }
8 node = node.children[char]
9 }
10
11 return node.isEnd
12}
13
14// Example
15func main() {
16 trie := NewTrie()
17 trie.Insert("cat")
18
19 fmt.Println(trie.Search("cat")) // true
20 fmt.Println(trie.Search("ca")) // false (not a complete word)
21 fmt.Println(trie.Search("dog")) // false
22}
Time: $O(m)$
Space: $O(1)$
3. StartsWith (Prefix Search)
1func (t *Trie) StartsWith(prefix string) bool {
2 node := t.root
3
4 for _, char := range prefix {
5 if node.children[char] == nil {
6 return false
7 }
8 node = node.children[char]
9 }
10
11 return true
12}
13
14// Example
15func main() {
16 trie := NewTrie()
17 trie.Insert("apple")
18
19 fmt.Println(trie.StartsWith("app")) // true
20 fmt.Println(trie.StartsWith("appl")) // true
21 fmt.Println(trie.StartsWith("ban")) // false
22}
Time: $O(m)$
Space: $O(1)$
4. Delete
1func (t *Trie) Delete(word string) bool {
2 return t.deleteHelper(t.root, word, 0)
3}
4
5func (t *Trie) deleteHelper(node *TrieNode, word string, index int) bool {
6 if index == len(word) {
7 if !node.isEnd {
8 return false // Word doesn't exist
9 }
10
11 node.isEnd = false
12
13 // Return true if node has no children (can be deleted)
14 return len(node.children) == 0
15 }
16
17 char := rune(word[index])
18 child := node.children[char]
19
20 if child == nil {
21 return false // Word doesn't exist
22 }
23
24 shouldDeleteChild := t.deleteHelper(child, word, index+1)
25
26 if shouldDeleteChild {
27 delete(node.children, char)
28 // Return true if current node has no children and is not end of another word
29 return len(node.children) == 0 && !node.isEnd
30 }
31
32 return false
33}
Time: $O(m)$
Space: $O(m)$ for recursion
5. Find All Words with Prefix
1func (t *Trie) FindWordsWithPrefix(prefix string) []string {
2 node := t.root
3
4 // Navigate to prefix
5 for _, char := range prefix {
6 if node.children[char] == nil {
7 return []string{}
8 }
9 node = node.children[char]
10 }
11
12 // Collect all words from this point
13 words := []string{}
14 t.collectWords(node, prefix, &words)
15 return words
16}
17
18func (t *Trie) collectWords(node *TrieNode, current string, words *[]string) {
19 if node.isEnd {
20 *words = append(*words, current)
21 }
22
23 for char, child := range node.children {
24 t.collectWords(child, current+string(char), words)
25 }
26}
27
28// Example
29func main() {
30 trie := NewTrie()
31 words := []string{"cat", "car", "card", "care", "careful", "dog"}
32 for _, word := range words {
33 trie.Insert(word)
34 }
35
36 fmt.Println(trie.FindWordsWithPrefix("car"))
37 // Output: [car, card, care, careful]
38}
Time: $O(p + n)$ where $p$ is prefix length, $n$ is number of nodes in subtree
Space: $O(n)$
Advanced Operations
Autocomplete
1func (t *Trie) Autocomplete(prefix string, maxResults int) []string {
2 node := t.root
3
4 // Navigate to prefix
5 for _, char := range prefix {
6 if node.children[char] == nil {
7 return []string{}
8 }
9 node = node.children[char]
10 }
11
12 // Collect words with limit
13 words := []string{}
14 t.collectWordsLimited(node, prefix, &words, maxResults)
15 return words
16}
17
18func (t *Trie) collectWordsLimited(node *TrieNode, current string, words *[]string, limit int) {
19 if len(*words) >= limit {
20 return
21 }
22
23 if node.isEnd {
24 *words = append(*words, current)
25 }
26
27 for char, child := range node.children {
28 if len(*words) >= limit {
29 return
30 }
31 t.collectWordsLimited(child, current+string(char), words, limit)
32 }
33}
Longest Common Prefix
1func (t *Trie) LongestCommonPrefix() string {
2 if t.root == nil || len(t.root.children) == 0 {
3 return ""
4 }
5
6 prefix := ""
7 node := t.root
8
9 for len(node.children) == 1 && !node.isEnd {
10 for char, child := range node.children {
11 prefix += string(char)
12 node = child
13 }
14 }
15
16 return prefix
17}
Word Count
1func (t *Trie) CountWords() int {
2 return t.countWordsHelper(t.root)
3}
4
5func (t *Trie) countWordsHelper(node *TrieNode) int {
6 count := 0
7
8 if node.isEnd {
9 count = 1
10 }
11
12 for _, child := range node.children {
13 count += t.countWordsHelper(child)
14 }
15
16 return count
17}
Complexity Analysis
| Operation | Time | Space |
|---|---|---|
| Insert | $O(m)$ | $O(m)$ |
| Search | $O(m)$ | $O(1)$ |
| StartsWith | $O(m)$ | $O(1)$ |
| Delete | $O(m)$ | $O(m)$ |
| Find All with Prefix | $O(p + n)$ | $O(n)$ |
where:
- $m$ = length of word
- $p$ = length of prefix
- $n$ = number of nodes in subtree
Space Complexity: $O(\text{ALPHABET_SIZE} \times N \times M)$ where:
- ALPHABET_SIZE = character set size
- N = number of words
- M = average word length
Optimizations
1. Array-Based Children (for small alphabets)
1const ALPHABET_SIZE = 26
2
3type TrieNodeArray struct {
4 children [ALPHABET_SIZE]*TrieNodeArray
5 isEnd bool
6}
7
8func charToIndex(ch rune) int {
9 return int(ch - 'a')
10}
11
12func (t *TrieArray) Insert(word string) {
13 node := t.root
14
15 for _, char := range word {
16 index := charToIndex(char)
17 if node.children[index] == nil {
18 node.children[index] = &TrieNodeArray{}
19 }
20 node = node.children[index]
21 }
22
23 node.isEnd = true
24}
Advantage: Faster access, $O(1)$ child lookup
Disadvantage: More memory if sparse
2. Compressed Trie (Radix Tree / Patricia Trie)
Store strings on edges instead of single characters.
1type RadixNode struct {
2 children map[string]*RadixNode
3 isEnd bool
4}
Advantage: Space-efficient for sparse tries
Example: "test" and "testing" share edge "test"
Applications
1. Autocomplete
1type AutocompleteSystem struct {
2 trie *Trie
3}
4
5func (a *AutocompleteSystem) AddWord(word string) {
6 a.trie.Insert(word)
7}
8
9func (a *AutocompleteSystem) GetSuggestions(prefix string, limit int) []string {
10 return a.trie.Autocomplete(prefix, limit)
11}
2. Spell Checker
1func (t *Trie) SpellCheck(word string) []string {
2 if t.Search(word) {
3 return []string{word} // Correct spelling
4 }
5
6 // Find similar words (simple version: edit distance 1)
7 suggestions := []string{}
8
9 // Try all possible single-character edits
10 for i := 0; i < len(word); i++ {
11 // Deletion
12 candidate := word[:i] + word[i+1:]
13 if t.Search(candidate) {
14 suggestions = append(suggestions, candidate)
15 }
16
17 // Substitution
18 for ch := 'a'; ch <= 'z'; ch++ {
19 candidate := word[:i] + string(ch) + word[i+1:]
20 if t.Search(candidate) {
21 suggestions = append(suggestions, candidate)
22 }
23 }
24 }
25
26 return suggestions
27}
3. IP Routing (Longest Prefix Match)
1type IPTrie struct {
2 root *TrieNode
3}
4
5func (ipt *IPTrie) AddRoute(ipPrefix string, nextHop string) {
6 // Store routing information in trie
7 // Each bit of IP becomes a path in trie
8}
9
10func (ipt *IPTrie) LongestPrefixMatch(ip string) string {
11 // Find longest matching prefix for routing
12 return ""
13}
4. Word Search in Grid
1func FindWords(board [][]byte, words []string) []string {
2 // Build trie from words
3 trie := NewTrie()
4 for _, word := range words {
5 trie.Insert(word)
6 }
7
8 result := []string{}
9 visited := make([][]bool, len(board))
10 for i := range visited {
11 visited[i] = make([]bool, len(board[0]))
12 }
13
14 // DFS from each cell
15 for i := 0; i < len(board); i++ {
16 for j := 0; j < len(board[0]); j++ {
17 dfsWordSearch(board, i, j, trie.root, "", &result, visited)
18 }
19 }
20
21 return result
22}
23
24func dfsWordSearch(board [][]byte, i, j int, node *TrieNode, current string, result *[]string, visited [][]bool) {
25 if i < 0 || i >= len(board) || j < 0 || j >= len(board[0]) || visited[i][j] {
26 return
27 }
28
29 char := rune(board[i][j])
30 if node.children[char] == nil {
31 return
32 }
33
34 visited[i][j] = true
35 current += string(char)
36 node = node.children[char]
37
38 if node.isEnd {
39 *result = append(*result, current)
40 node.isEnd = false // Avoid duplicates
41 }
42
43 // Explore all 4 directions
44 dfsWordSearch(board, i+1, j, node, current, result, visited)
45 dfsWordSearch(board, i-1, j, node, current, result, visited)
46 dfsWordSearch(board, i, j+1, node, current, result, visited)
47 dfsWordSearch(board, i, j-1, node, current, result, visited)
48
49 visited[i][j] = false // Backtrack
50}
Trie vs. Hash Table
| Feature | Trie | Hash Table |
|---|---|---|
| Search | $O(m)$ | $O(1)$ average |
| Prefix Search | $O(p)$ | $O(n)$ (scan all keys) |
| Space | $O(ALPHABET \times N \times M)$ | $O(N \times M)$ |
| Ordered | Yes (lexicographic) | No |
| Collision | No | Yes |
| Autocomplete | Efficient | Inefficient |
When to Use Trie
✅ Use when:
- Prefix-based operations (autocomplete, spell check)
- Dictionary with many words sharing prefixes
- IP routing (longest prefix match)
- Word games (Boggle, Scrabble)
- T9 predictive text
❌ Don't use when:
- Simple exact-match lookups (use hash table)
- Memory is very limited
- Alphabet size is huge (use compressed trie)
- No prefix operations needed
Complete Implementation
1package main
2
3import "fmt"
4
5type TrieNode struct {
6 children map[rune]*TrieNode
7 isEnd bool
8 count int // Number of words passing through this node
9}
10
11type Trie struct {
12 root *TrieNode
13}
14
15func NewTrie() *Trie {
16 return &Trie{
17 root: &TrieNode{
18 children: make(map[rune]*TrieNode),
19 },
20 }
21}
22
23func (t *Trie) Insert(word string) {
24 node := t.root
25
26 for _, char := range word {
27 if node.children[char] == nil {
28 node.children[char] = &TrieNode{
29 children: make(map[rune]*TrieNode),
30 }
31 }
32 node = node.children[char]
33 node.count++
34 }
35
36 node.isEnd = true
37}
38
39func (t *Trie) Search(word string) bool {
40 node := t.root
41
42 for _, char := range word {
43 if node.children[char] == nil {
44 return false
45 }
46 node = node.children[char]
47 }
48
49 return node.isEnd
50}
51
52func (t *Trie) StartsWith(prefix string) bool {
53 node := t.root
54
55 for _, char := range prefix {
56 if node.children[char] == nil {
57 return false
58 }
59 node = node.children[char]
60 }
61
62 return true
63}
64
65func (t *Trie) CountWordsWithPrefix(prefix string) int {
66 node := t.root
67
68 for _, char := range prefix {
69 if node.children[char] == nil {
70 return 0
71 }
72 node = node.children[char]
73 }
74
75 return node.count
76}
77
78func main() {
79 trie := NewTrie()
80
81 words := []string{"cat", "car", "card", "care", "careful", "dog", "dodge"}
82 for _, word := range words {
83 trie.Insert(word)
84 }
85
86 fmt.Println("Search 'car':", trie.Search("car")) // true
87 fmt.Println("Search 'ca':", trie.Search("ca")) // false
88 fmt.Println("StartsWith 'car':", trie.StartsWith("car")) // true
89 fmt.Println("Words with prefix 'car':", trie.CountWordsWithPrefix("car")) // 4
90}
Related Snippets
- Binary Search
Binary search is an efficient algorithm for finding a target value in a sorted … - Binary Tree
A binary tree is a tree data structure where each node has at most two children, … - General Tree
A tree is a hierarchical data structure consisting of nodes connected by edges, … - Heap Data Structure
A heap is a specialized tree-based data structure that satisfies the heap … - Heap Sort
Heap sort is a comparison-based sorting algorithm that uses a binary heap data … - Linked List Operations
Optimal techniques for common linked list operations using pointer manipulation, … - LRU and LFU Cache
Cache replacement policies determine which items to evict when the cache is … - Merge Sort
Merge sort is a stable, comparison-based sorting algorithm that uses the … - Quadtree
A Quadtree is a tree data structure where each internal node has exactly four … - Quick Sort
Quick sort is an efficient, in-place, comparison-based sorting algorithm that … - String Similarity Algorithms
Algorithms for measuring similarity between strings, used in spell checking, DNA … - Terrain & Heightmap Generation
Procedural terrain generation creates realistic landscapes using mathematical … - Wave Function Collapse
Wave Function Collapse (WFC) is a procedural generation algorithm that creates …