Skip to main content

Tries (Prefix Trees theory)

knoxiboy
EditReport

Trie Data Structure

Introductionโ€‹

A Trie (also called a Prefix Tree) is a special type of tree used to store collections of strings. It is commonly used for tasks involving prefix-based search, such as autocomplete, dictionary implementations, and spell checking.

The key idea of a Trie is to use the prefixes of words to share common parts and reduce memory usage. Each node in a Trie represents a single character, and the paths from the root to a leaf represent words.

Video Explanationโ€‹

Key Conceptsโ€‹

  1. Root: The Trie starts from the root node, which is an empty node or a node with no character.
  2. Edges: Each edge represents a character from the alphabet.
  3. Nodes: Each node stores a character and points to its children (the next characters in words that share the same prefix).
  4. End of Word: Some nodes are marked as "end of word" nodes, indicating that the characters from the root to this node form a valid word.

Operationsโ€‹

1. Insert Operationโ€‹

The insert operation is used to add a word to the Trie. The process is as follows:

  • Start from the root and for each character in the word, check if it already exists as a child of the current node.
  • If not, create a new node for that character.
  • Mark the final node as the "end of word" to signify that the path represents a complete word.

2. Search Operationโ€‹

The search operation checks whether a given word exists in the Trie:

  • Start at the root and for each character in the word, check if it exists as a child node.
  • If you reach the end of the word and the current node is marked as the "end of word", then the word exists in the Trie.

A common use case of a Trie is to find all words that start with a given prefix:

  • Traverse the Trie using the characters of the prefix.
  • Once the prefix is found, collect all the words that follow from the remaining nodes.

Exampleโ€‹

Here is a simple visualization of a Trie that stores the words "cat", "car", "dog", and "dot":

In this Trie:

  • "cat", "car", "dog", and "dot" are stored.
  • The nodes marked as (EOW) indicate the "end of word".

Time Complexityโ€‹

  • Insert: O(m) where m is the length of the word being inserted.
  • Search: O(m) where m is the length of the word being searched.
  • Prefix Search: O(m + k) where m is the length of the prefix and k is the number of characters in the words that match the prefix.

Advantages of Trieโ€‹

  1. Efficient for Prefix Search: Tries are optimal for prefix searches, as they can find all words with a given prefix in linear time relative to the length of the prefix.
  2. Reduced Space for Shared Prefixes: By sharing common prefixes, Tries can reduce the memory usage for storing large sets of related words.

Disadvantages of Trieโ€‹

  1. Memory Usage: While Tries are efficient in terms of search, they may use more memory than other data structures like hash maps if there are many different words with few shared prefixes.
  2. Complexity: Implementing a Trie from scratch can be more complex compared to other data structures.

Common Applicationsโ€‹

  • Autocomplete systems: Finding all words that begin with a given prefix.
  • Spell checking: Checking if a given word exists in a dictionary.
  • IP routing: Tries can be used to represent routing tables in networking.

Example Code (C++)โ€‹

#include <iostream>
#include <unordered_map>
using namespace std;

struct TrieNode {
unordered_map<char, TrieNode*> children;
bool isEndOfWord;

TrieNode() {
isEndOfWord = false;
}
};

class Trie {
TrieNode* root;
public:
Trie() {
root = new TrieNode();
}

// Insert a word into the Trie
void insert(string word) {
TrieNode* node = root;
for (char c : word) {
if (!node->children.count(c)) {
node->children[c] = new TrieNode();
}
node = node->children[c];
}
node->isEndOfWord = true;
}

// Search for a word in the Trie
bool search(string word) {
TrieNode* node = root;
for (char c : word) {
if (!node->children.count(c)) return false;
node = node->children[c];
}
return node->isEndOfWord;
}
};

int main() {
Trie trie;
trie.insert("cat");
trie.insert("car");
trie.insert("dog");

cout << trie.search("cat") << endl; // Outputs: 1 (true)
cout << trie.search("car") << endl; // Outputs: 1 (true)
cout << trie.search("bat") << endl; // Outputs: 0 (false)

return 0;
}

Complexity Cheat Sheetโ€‹

OperationBest CaseAverage CaseWorst CaseSpace Complexity
InsertionO(k)O(k)O(k)O(k)O(k)O(k)O(k)O(k)
SearchO(k)O(k)O(k)O(k)O(k)O(k)O(1)O(1)
Prefix SearchO(p)O(p)O(p)O(p)O(p)O(p)O(1)O(1)
DeletionO(k)O(k)O(k)O(k)O(k)O(k)O(k)O(k)
Overall Trie Storage---O(Nร—k)O(N \times k)

Here kk is the length of the key/word, pp is the prefix length, and NN is the number of keys stored.

Track Your Progress

Done with this topic? Mark it as complete to track your progress.

๐Ÿ’ฌ Discuss this page

Have a question or spot something confusing in "Tries (Prefix Trees theory)"? Ask below โ€” it's backed by GitHub Discussions, so maintainers get notified like any other GitHub activity.