Trie (dibaca “try”, dari retrieval) adalah tree khusus untuk menyimpan himpunan string dengan prefix sharing. Setiap node merepresentasikan satu karakter, dan path dari root ke node manapun merepresentasikan sebuah prefix. Trie menjawab pertanyaan yang sulit dijawab BST atau hash table: “apakah ada kata dengan prefix app?” atau “apa saja kata yang dimulai dengan app?” dengan efisien. Aplikasi praktisnya luas: autocomplete di Google Search, koreksi ejaan, IP routing table (longest prefix match), dan kamus T9 di ponsel lama. Pada Chapter ini kita akan mengimplementasikan trie dengan operasi insert, search, dan startsWith, menganalisis trade-off memori, dan melihat variasi optimasi (compressed trie / radix trie).
Struktur Trie #
Setiap node trie berisi:
- Pointer ke anak-anak per karakter (biasanya array berukuran alphabet size atau
std::unordered_map). - Flag
is_endmenandakan bahwa node ini adalah akhir dari sebuah kata yang disimpan.
Pada diagram di atas, trie menyimpan kata aa, app, apple, bat. Path root → a → p → p berakhir di node yang menandai akhir kata app. Node yang sama adalah ancestor dari apple. Inilah prefix sharing: kata app dan apple berbagi prefix app.
Implementasi Dasar #
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
class Trie {
public:
Trie() : root_(new TrieNode()) {}
~Trie() { destroy(root_); }
void insert(const std::string& word) {
TrieNode* cur = root_;
for (char c : word) {
if (cur->children.find(c) == cur->children.end()) {
cur->children[c] = new TrieNode();
}
cur = cur->children[c];
}
cur->is_end = true;
}
bool search(const std::string& word) const {
const TrieNode* node = find_node(word);
return node != nullptr && node->is_end;
}
bool starts_with(const std::string& prefix) const {
return find_node(prefix) != nullptr;
}
private:
struct TrieNode {
std::unordered_map<char, TrieNode*> children;
bool is_end = false;
};
TrieNode* root_;
const TrieNode* find_node(const std::string& s) const {
const TrieNode* cur = root_;
for (char c : s) {
auto it = cur->children.find(c);
if (it == cur->children.end()) return nullptr;
cur = it->second;
}
return cur;
}
void destroy(TrieNode* node) {
for (auto& kv : node->children) destroy(kv.second);
delete node;
}
};
int main() {
Trie trie;
trie.insert("apple");
trie.insert("app");
trie.insert("bat");
trie.insert("ba");
std::cout << "search(\"apple\") : " << (trie.search("apple") ? "ya" : "tidak") << '\n';
std::cout << "search(\"app\") : " << (trie.search("app") ? "ya" : "tidak") << '\n';
std::cout << "search(\"appl\") : " << (trie.search("appl") ? "ya" : "tidak") << '\n';
std::cout << "search(\"bat\") : " << (trie.search("bat") ? "ya" : "tidak") << '\n';
std::cout << "search(\"cat\") : " << (trie.search("cat") ? "ya" : "tidak") << '\n';
std::cout << '\n';
std::cout << "starts_with(\"app\") : " << (trie.starts_with("app") ? "ya" : "tidak") << '\n';
std::cout << "starts_with(\"ba\") : " << (trie.starts_with("ba") ? "ya" : "tidak") << '\n';
std::cout << "starts_with(\"ca\") : " << (trie.starts_with("ca") ? "ya" : "tidak") << '\n';
std::cout << "starts_with(\"bats\"): " << (trie.starts_with("bats")? "ya" : "tidak") << '\n';
return 0;
}Output yang Dihasilkan:
search("apple") : ya
search("app") : ya
search("appl") : tidak
search("bat") : ya
search("cat") : tidak
starts_with("app") : ya
starts_with("ba") : ya
starts_with("ca") : tidak
starts_with("bats"): tidak- Struct
TrieNode: pakaistd::unordered_map<char, TrieNode*>untuk children. Untuk alphabet kecil (mis. huruf kecil a-z), arrayTrieNode*[26]lebih cepat dan hemat overhead per node. Map fleksibel untuk Unicode atau alphabet besar. insert: traverse per karakter. Bila child belum ada, buat node baru. Setelah loop, setis_end = trueuntuk menandai akhir kata.search: traverse per karakter. Bila child tidak ada → kata tidak tersimpan. Bila traverse selesai → cekis_end. Pencarian “appl” gagal karena tidak ada nodeis_endsetelah patha-p-p-l(leaf tidak punyais_end).starts_with: hanya traverse, tidak peduliis_end. Cukup cek apakah path prefix ada.search("app") = yatapisearch("appl") = tidak: kataappdisimpan sebagai path root→a→p→p denganis_end=true. Kataappltidak disimpan; meskipun path ada (sebagai prefix dariapple),is_endtidak diset.
Kompleksitas Trie #
| Operasi | Waktu | Catatan |
|---|---|---|
Insert kata s panjang m | O(m) | Traverse m karakter |
Search kata s | O(m) | Sama dengan insert |
starts_with prefix p | O(p) | Hanya traverse prefix |
Prefix search (semua kata mulai p) | O(p + jumlah hasil) | BFS/DFS dari node prefix |
| Memori | O(total karakter semua kata) | Sering lebih besar dari hash table |
Trie tidak bergantung pada jumlah kata (n) — hanya pada panjang kata (m). Untuk kata panjang rata-rata 10 karakter, insert/search 10 langkah terlepas dari jumlah kata dalam kamus 1 juta. Hash table juga O(m) average untuk hash, namun trie unggul pada prefix query.
Autocomplete dengan Trie #
Salah satu aplikasi paling natural trie: ambil semua kata yang dimulai dengan prefix tertentu.
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
class AutocompleteTrie {
public:
void insert(const std::string& word) {
Node* cur = &root_;
for (char c : word) {
auto it = cur->children.find(c);
if (it == cur->children.end()) {
cur->children[c] = Node{};
}
cur = &cur->children[c];
}
cur->is_end = true;
}
std::vector<std::string> suggest(const std::string& prefix) const {
const Node* cur = &root_;
std::string path;
for (char c : prefix) {
auto it = cur->children.find(c);
if (it == cur->children.end()) return {};
cur = &it->second;
path.push_back(c);
}
std::vector<std::string> results;
dfs(cur, path, results);
return results;
}
private:
struct Node {
std::unordered_map<char, Node> children;
bool is_end = false;
};
Node root_;
void dfs(const Node* node, std::string& path, std::vector<std::string>& out) const {
if (node->is_end) out.push_back(path);
for (const auto& kv : node->children) {
path.push_back(kv.first);
dfs(&kv.second, path, out);
path.pop_back();
}
}
};
int main() {
AutocompleteTrie trie;
for (const std::string& w : {
"applause", "apple", "application", "apply", "appoint", "banana", "band"
}) {
trie.insert(w);
}
for (const std::string& prefix : {"app", "ban", "xyz"}) {
std::cout << "Suggest(\"" << prefix << "\"): ";
auto results = trie.suggest(prefix);
for (const auto& s : results) std::cout << s << ' ';
std::cout << '\n';
}
return 0;
}Output yang Dihasilkan:
Suggest("app"): appoint apply application apple applause
Suggest("ban"): band banana
Suggest("xyz"): Nodepakaistd::unordered_map<char, Node>(in-place): tidak perlu pointer alokasi dinamis. Node disimpan di dalam map sebagai value. Lebih sederhana, namun tidak bisa di-copy dengan mudah.suggest: pertama traverse prefix untuk menemukan node tujuan. Bila tidak ada → return kosong. Bila ada → DFS untuk kumpulkan semua kata yang adais_end = truedi subtree.- DFS backtrack: dorong karakter ke
pathsebelum rekursi, pop setelahnya — pola backtracking klasik. - Hasil
Suggest("app"): 5 kata mulai denganapp. Output urutannya bergantung iterasiunordered_map(non-deterministik antar compiler, tetapi konsisten dalam satu run).
Variasi dan Optimasi Trie #
| Variasi | Karakteristik | Cocok untuk |
|---|---|---|
| Standard trie | Node per karakter | Pencarian umum |
| Compressed trie (radix) | Edge berisi substring | Hemat memori (IP routing) |
| Suffix trie | Semua suffix dari satu string | Pattern matching, bioinformatika |
| Ternary search tree | BST per node dengan 3 anak | Hemat memori, dictionary spell-checker |
Ringkasan #
- Trie adalah tree khusus untuk string: setiap node = satu karakter, path root→node = prefix.
- Tiga operasi dasar:
insert(O(m)),search(O(m)),starts_with(O(m)) —madalah panjang kata/prefix, tidak bergantung jumlah kata dalam trie. - Prefix sharing menjadikan trie efisien untuk autocomplete dan prefix query — operasi yang sulit di hash table atau BST.
- Trade-off: memori besar (setiap karakter punya node sendiri). Compressed trie menggabungkan edge berderajat 1 untuk mengurangi memori.
- Aplikasi nyata: autocomplete search engine, spell-checker, IP routing (longest prefix match), T9 dictionary, DNA sequence matching (suffix trie).