Your Progress
70% complete. Continue learning to master DSA!
Tries (Prefix Trees)
A trie is a tree-like data structure that efficiently stores and retrieves keys in a dataset of strings. Also known as prefix trees, tries excel at tasks like autocomplete, spell checking, and IP routing.
Key Takeaways
- Tries provide O(m) lookup time where m is the length of the key
- They efficiently store strings with common prefixes to save space
- Each node in a trie may contain up to 26 children (for English alphabet) or more
- Tries excel at prefix matching, making them perfect for autocomplete features
- They're more efficient than hash tables for specific string operations like prefix searches
What is a Trie?
A trie (pronounced "try" or "tree") is a specialized tree-based data structure that is used to store a dynamic set or associative array where the keys are usually strings. Unlike a binary search tree, no node in the tree stores the key associated with that node; instead, its position in the tree defines the key with which it is associated.
The name "trie" comes from the word "retrieval," highlighting its primary purpose: fast retrieval of stored data. Tries are also known as prefix trees, digital trees, or radix trees (although radix trees are a more specific type of trie).
Key Characteristics of Tries
- Root Node: The root node of a trie is typically empty
- Branching: Each node may have multiple children, one for each possible character
- Path to Node: The path from the root to a node represents a prefix of one or more strings
- End Markers: Special markers (or flags) indicate when a complete word/string is formed
- Space Efficiency: Common prefixes are stored only once, saving memory
Trie Structure and Representation
A trie consists of nodes connected by edges. Each node in a trie represents a single character, and edges connect characters that appear sequentially in stored strings.
Basic Structure
Here's what a trie node typically contains:
- An array or map of child nodes (one for each possible character)
- A boolean flag indicating if the node represents the end of a valid word
Visually, a trie storing the words "car", "cat", "card", and "care" would look like this:
(root) | c | a / \ r t /|\ d e
In this trie, following the path from root → c → a → r would represent the prefix "car". The nodes for 'd', 'e', and 't' would be marked as word endpoints.
Node Implementation
A typical trie node class or structure in JavaScript:
class TrieNode { constructor() { // Children map with character as key and TrieNode as value this.children = new Map(); // Flag to indicate if this node represents the end of a word this.isEndOfWord = false; } } class Trie { constructor() { // Root of the trie this.root = new TrieNode(); } // Other methods will be added here }
Some implementations may use a fixed-size array for the children (e.g., size 26 for lowercase English letters), while others use a hash map or object for flexibility and to handle larger character sets.
Basic Trie Operations
A trie supports several standard operations that make it useful for string manipulation and searching:
1. Insertion
To insert a string into a trie, we start at the root and follow these steps for each character:
- Check if a node for the current character exists
- If not, create a new node for this character
- Move to the next character and the corresponding node
- After processing all characters, mark the last node as the end of a word
// Insert a word into the trie insert(word) { let current = this.root; for (let i = 0; i < word.length; i++) { const char = word[i]; // If the character doesn't exist in the children map, add it if (!current.children.has(char)) { current.children.set(char, new TrieNode()); } // Move to the next node current = current.children.get(char); } // Mark the end of the word current.isEndOfWord = true; }
2. Search
To search for a word, we follow a similar process as insertion but without creating new nodes:
// Search for a word in the trie search(word) { let current = this.root; for (let i = 0; i < word.length; i++) { const char = word[i]; // If the character doesn't exist in the children map, the word is not in the trie if (!current.children.has(char)) { return false; } // Move to the next node current = current.children.get(char); } // Return true only if the current node is marked as the end of a word return current.isEndOfWord; }
3. Prefix Search
One of the key advantages of tries is the ability to efficiently find all words with a given prefix:
// Check if there is any word in the trie that starts with the given prefix startsWith(prefix) { let current = this.root; for (let i = 0; i < prefix.length; i++) { const char = prefix[i]; // If the character doesn't exist in the children map, no word with this prefix exists if (!current.children.has(char)) { return false; } // Move to the next node current = current.children.get(char); } // If we reach here, the prefix exists in the trie return true; }
4. Deletion
Deletion from a trie is more complex than the other operations. We need to:
- Find the node representing the last character of the word
- Mark it as not the end of a word (if needed)
- Remove nodes that are not used by any other word (to save space)
Deletion is typically implemented recursively, working from the end of the word back to the root.
Complete Trie Implementation
Here's a complete JavaScript implementation of a Trie with all the basic operations:
class TrieNode { constructor() { this.children = new Map(); this.isEndOfWord = false; } } class Trie { constructor() { this.root = new TrieNode(); } // Insert a word into the trie insert(word) { let current = this.root; for (let i = 0; i < word.length; i++) { const char = word[i]; if (!current.children.has(char)) { current.children.set(char, new TrieNode()); } current = current.children.get(char); } current.isEndOfWord = true; } // Search for a word in the trie search(word) { let current = this.root; for (let i = 0; i < word.length; i++) { const char = word[i]; if (!current.children.has(char)) { return false; } current = current.children.get(char); } return current.isEndOfWord; } // Check if there is any word in the trie that starts with the given prefix startsWith(prefix) { let current = this.root; for (let i = 0; i < prefix.length; i++) { const char = prefix[i]; if (!current.children.has(char)) { return false; } current = current.children.get(char); } return true; } // Delete a word from the trie delete(word) { this._deleteRecursive(this.root, word, 0); } // Helper method for delete _deleteRecursive(current, word, index) { // Base case: end of word reached if (index === word.length) { // Word exists in trie, so unmark the end of word if (current.isEndOfWord) { current.isEndOfWord = false; } // Return true if node has no children (can be deleted) return current.children.size === 0; } const char = word[index]; const node = current.children.get(char); // Word not found in trie if (!node) { return false; } // Recursively delete for next character const shouldDeleteNode = this._deleteRecursive(node, word, index + 1); // If child node can be deleted, remove it from the map if (shouldDeleteNode) { current.children.delete(char); // Current node can be deleted if it's not the end of another word // and has no other children return !current.isEndOfWord && current.children.size === 0; } return false; } } // Example usage const trie = new Trie(); trie.insert("apple"); trie.insert("app"); trie.insert("application"); console.log(trie.search("apple")); // true console.log(trie.search("app")); // true console.log(trie.search("appl")); // false console.log(trie.startsWith("app")); // true trie.delete("app"); console.log(trie.search("app")); // false console.log(trie.search("apple")); // true console.log(trie.startsWith("app")); // true
Applications of Tries
Tries are extremely useful in a variety of applications that involve string processing:
Autocomplete Systems
Tries can quickly find all words that start with a given prefix, making them ideal for search suggestions and autocomplete features.
Spell Checkers
Tries can verify if a word exists in a dictionary and suggest corrections for misspelled words.
IP Routing
PATRICIA tries (a variant of tries) are used in network routers for efficient IP address lookup.
Text Processing
Used in implementing data compression algorithms, text search algorithms like Aho-Corasick, and more.
Predictive Text
Mobile keyboards use tries to predict the next word based on input patterns.
Longest Prefix Matching
Efficient algorithms for finding the longest matching prefix among a set of strings.
Time and Space Complexity
Operation | Time Complexity | Notes |
---|---|---|
Insertion | O(m) | Where m is the length of the key |
Search | O(m) | Where m is the length of the key |
Prefix Search | O(p) | Where p is the length of the prefix |
Deletion | O(m) | Where m is the length of the key |
Space Complexity
The space complexity of a trie is more complex to analyze:
- Worst case: O(n × m), where n is the number of keys and m is the length of the longest key
- Best case: O(n) when all keys share most of their prefixes
- Each node may need to store references to up to 26 children (for English alphabet) or more for larger character sets
While tries can be memory-intensive, they offer trade-offs with speed that make them valuable for many applications.
Conclusion
Tries are powerful data structures that excel at string operations, particularly those involving prefixes. While they may consume more memory than some alternatives, their speed and flexibility make them an essential tool for any programmer working with string data.
Remember
When deciding whether to use a trie, consider:
- Whether prefix operations are important to your application
- The memory constraints of your system
- The trade-off between time and space complexity
- Whether you need to support additional operations like spell-checking or autocomplete
Next Steps
To deepen your understanding of tries and related data structures, explore these topics:
Related Tutorials
Related Tutorials
Hash Tables
Compare Tries with another efficient data structure for string operations.
Learn moreTrees
Learn about the foundational data structure behind Tries.
Learn moreString Algorithms
Explore more advanced algorithms for string processing.
Learn more