Implement Trie (Prefix Tree)

A Trie, also known as a “prefix tree,” is a tree-like data structure used for storing a dynamic set of strings. Tries are useful for operations like searching, inserting, and deleting strings. One key advantage is that Tries can quickly find all keys with a common prefix, making them useful for auto-complete features and dictionaries.

In a Trie, each node represents a single character of a string, and each path from the root to a node represents a prefix of strings in the set. The leaf nodes correspond to complete strings.

Example Code in Java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class TrieNode {
    boolean isEnd;
    TrieNode[] children = new TrieNode[26];
}

public class Trie {
    private TrieNode root;

    public Trie() {
        root = new TrieNode();
    }

    public void insert(String word) {
        TrieNode node = root;
        for (char c : word.toCharArray()) {
            if (node.children[c - 'a'] == null) {
                node.children[c - 'a'] = new TrieNode();
            }
            node = node.children[c - 'a'];
        }
        node.isEnd = true;
    }
    
    // Other methods like search, delete
}

Example Code in C++

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <vector>

struct TrieNode {
    bool isEnd;
    std::vector<TrieNode*> children;

    TrieNode() : isEnd(false), children(26, nullptr) {}
};

class Trie {
public:
    TrieNode* root;

    Trie() {
        root = new TrieNode();
    }

    void insert(std::string word) {
        TrieNode* node = root;
        for (char c : word) {
            if (!node->children[c - 'a']) {
                node->children[c - 'a'] = new TrieNode();
            }
            node = node->children[c - 'a'];
        }
        node->isEnd = true;
    }
    
    // Other methods like search, delete
};

Example Code in Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class TrieNode:
    def __init__(self):
        self.isEnd = False
        self.children = {}

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.isEnd = True
    
    # Other methods like search, delete

With this implementation, you can perform various string operations efficiently. Each operation like insert, search, and delete can be done in ( O(n) ) time complexity, where ( n ) is the length of the word.

A Trie is a specialized tree structure where each node represents a part of a string. It’s used to efficiently store a large set of strings. Let’s implement the Trie class as described:

  1. Initialization (__init__ method): Create a root node with an empty dictionary to store children nodes and a boolean flag to mark the end of a word.

  2. Insertion (insert method): Add a word to the Trie, starting from the root and following the characters of the word.

  3. Search (search method): Check if a word exists in the Trie.

  4. Prefix Search (startsWith method): Check if there’s any word in the Trie that starts with a given prefix.

Here’s the code implementing the Trie class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Trie:

    def __init__(self):
        self.root = {'*': False} # '*' marks the end of a word

    def insert(self, word: str) -> None:
        node = self.root
        for char in word:
            # Add a new node if character not in Trie
            if char not in node:
                node[char] = {'*': False}
            node = node[char]
        node['*'] = True # Mark the end of the word

    def search(self, word: str) -> bool:
        node = self.root
        for char in word:
            # If character not found, return False
            if char not in node:
                return False
            node = node[char]
        return node['*'] # Return True if it's the end of a word

    def startsWith(self, prefix: str) -> bool:
        node = self.root
        for char in prefix:
            # If character not found, return False
            if char not in node:
                return False
            node = node[char]
        return True # Return True if prefix found

Explanation:

  • The root node is a dictionary, and each character in the strings is a key in the dictionary. The value associated with each key is another dictionary, representing the child nodes.
  • The special key '*' is used to mark the end of a word.
  • The insert method builds the Trie by adding nodes for each character in the word.
  • The search method traverses the Trie following the characters of the word and checks if the end of the word is reached.
  • The startsWith method is similar to search but returns True if the prefix is found, regardless of whether it’s the end of a word.

The operations of insert, search, and startsWith have a time complexity of O(m), where m is the length of the word or prefix being processed. The space complexity of the Trie is O(n), where n is the total number of characters in all the words stored in the Trie.

Applications

  1. Autocomplete
  2. Spell checker
  3. IP routing (longest prefix matching)

Also known as prefix tree. We can efficiently match prefixes.

Why trie?

  • Uses less space than hashtable
  1. How can we implement the insert?

    • How to create root?
      • links size is 26 (will hold the characters)
    • Root does not store anything
  2. Create empty links and define a end_of_word boolean In the constructor

  3. Insert operation: Traverse the characters in the word Pick the first character Check if it is already in the root in the links held by the root If it is not: Create a new trie node instance and set it’s character value to the first letter Outside the loop, mark the end_of_word = true for the last character

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class TrieNode
    attr_accessor :end_of_word, :links

    def initialize()
        @links = {}
        @end_of_word = false
     end

end

class Trie

=begin
    Initialize your data structure here.
=end
    def initialize()
        @root = TrieNode.new
    end


=begin
    Inserts a word into the trie.
    :type word: String
    :rtype: Void
=end
    def insert(word)
        node = @root
        for i in (0..word.size - 1)
            letter = word[i]
            if !node.links.key?(letter)
                node.links[letter] = TrieNode.new
            end
            node = node.links[letter]
        end
        node.end_of_word = true
    end


=begin
    Returns if the word is in the trie.
    :type word: String
    :rtype: Boolean
=end
    def search(word)
        node = @root
        for i in (0..word.size - 1)
           letter = word[i]
            if !node.links.key?(letter)
                return false
            end
            node = node.links[letter]
        end
        return node.end_of_word
    end


=begin
    Returns if there is any word in the trie that starts with the given prefix.
    :type prefix: String
    :rtype: Boolean
=end
    def starts_with(prefix)
        node = @root
        for i in (0..prefix.size - 1)
           letter = prefix[i]
            if !node.links.key?(letter)
                return false
            end
            node = node.links[letter]
        end
        
        return true
    end
end

# Your Trie object will be instantiated and called as such:
# obj = Trie.new()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.starts_with(prefix)