🔤 Trie (Prefix Tree)

🔴 Advanced Advanced 60 min read

🎯 What is a Trie?

A Trie (pronounced "try") is a tree-like data structure for efficiently storing and searching strings. Each node represents a character, and paths from root to leaf form complete words. Tries enable O(L) operations where L is word length - independent of the total number of stored words!
Root
 │
 ├── c → a → t (✓)  ← word "cat"
 │         └── s (✓) ← word "cats"
 │
 ├── d → o → g (✓)  ← word "dog"
 │
 └── b → a → t (✓)  ← word "bat"
           └── s (✓) ← word "bats"

✓ marks end of word

💻 PHP Implementation

<?php
class TrieNode {
    /** @var array */
    public array $children = [];
    public bool $isEndOfWord = false;
}

class Trie {
    private TrieNode $root;

    public function __construct() {
        $this->root = new TrieNode();
    }

    // Insert word - O(L) where L = word length
    public function insert(string $word): void {
        $node = $this->root;
        for ($i = 0; $i < strlen($word); $i++) {
            $char = $word[$i];
            if (!isset($node->children[$char])) {
                $node->children[$char] = new TrieNode();
            }
            $node = $node->children[$char];
        }
        $node->isEndOfWord = true;
    }

    // Search exact word - O(L)
    public function search(string $word): bool {
        $node = $this->getNode($word);
        return $node !== null && $node->isEndOfWord;
    }

    // Check if any word starts with prefix - O(L)
    public function startsWith(string $prefix): bool {
        return $this->getNode($prefix) !== null;
    }

    private function getNode(string $str): ?TrieNode {
        $node = $this->root;
        for ($i = 0; $i < strlen($str); $i++) {
            $char = $str[$i];
            if (!isset($node->children[$char])) return null;
            $node = $node->children[$char];
        }
        return $node;
    }

    // Get all words with given prefix (autocomplete)
    public function autocomplete(string $prefix): array {
        $result = [];
        $node = $this->getNode($prefix);
        if ($node !== null) {
            $this->collectWords($node, $prefix, $result);
        }
        return $result;
    }

    private function collectWords(TrieNode $node, string $currentWord, array &$result): void {
        if ($node->isEndOfWord) $result[] = $currentWord;
        foreach ($node->children as $char => $child) {
            $this->collectWords($child, $currentWord . $char, $result);
        }
    }

    // Delete word - O(L)
    public function delete(string $word): bool {
        return $this->deleteRec($this->root, $word, 0);
    }

    private function deleteRec(TrieNode $node, string $word, int $depth): bool {
        if ($depth === strlen($word)) {
            if (!$node->isEndOfWord) return false;
            $node->isEndOfWord = false;
            return empty($node->children);
        }
        $char = $word[$depth];
        if (!isset($node->children[$char])) return false;

        $shouldDelete = $this->deleteRec($node->children[$char], $word, $depth + 1);
        if ($shouldDelete) {
            unset($node->children[$char]);
            return empty($node->children) && !$node->isEndOfWord;
        }
        return false;
    }

    // Count words
    public function countWords(): int {
        return $this->countWordsRec($this->root);
    }

    private function countWordsRec(TrieNode $node): int {
        $count = $node->isEndOfWord ? 1 : 0;
        foreach ($node->children as $child) {
            $count += $this->countWordsRec($child);
        }
        return $count;
    }
}

$trie = new Trie();
$trie->insert("cat");
$trie->insert("cats");
$trie->insert("dog");
$trie->insert("bat");

var_dump($trie->search("cat"));     // true
var_dump($trie->search("ca"));      // false (not a word)
var_dump($trie->startsWith("ca"));  // true
print_r($trie->autocomplete("ca")); // ["cat", "cats"]
echo $trie->countWords();           // 4

📊 Complexity

Insert O(L) L = word length
Search O(L) Independent of total words!
Prefix Search O(L) startsWith check
Delete O(L)
Space O(N*L) N words × avg length (but shared prefixes save space)

🌍 Applications

  • Autocomplete / Typeahead: Google search suggestions
  • Spell Checker: Fast dictionary lookup
  • IP Routing: Longest prefix matching
  • Word Games: Boggle, Scrabble solver
  • Phone Directory: Contact search by prefix

💼 Interview Questions

Hash Table: O(1) average for exact search, but can't do prefix search. Trie: O(L) for all operations, naturally supports prefix queries and ordered traversal.

Store top K suggestions at each node during insert. Or: traverse from prefix node and collect all words via DFS/BFS. For large datasets, store only most frequent suggestions per node.

📝 Quick Quiz

Q1: Trie search time depends on:

Q2: Which operation is unique to Trie (not Hash Table)?