Trie (Prefix Tree) là cấu trúc dữ liệu cây dùng để lưu trữ và truy xuất chuỗi ký tự hiệu quả. Mỗi nút đại diện cho một ký tự, đường từ gốc đến nút tạo thành một tiền tố. Ứng dụng: tự động hoàn thành (autocomplete), kiểm tra chính tả, từ điển, routing IP.
Đặc điểm
- Thời gian tìm/chèn/xóa:
O(L)vớiLlà độ dài chuỗi. - Không gian:
O(ALPHABET_SIZE * N * L)(có thể nén bằng compressed trie). - Hỗ trợ duyệt theo thứ tự từ điển (lexicographical).
Cấu trúc nút
struct TrieNode {
TrieNode* children[ALPHABET_SIZE]; // Mảng con trỏ đến các nút con
bool isEndOfWord; // Đánh dấu kết thúc từ
TrieNode() : isEndOfWord(false) {
fill(begin(children), end(children), nullptr);
}
};
Các thao tác cơ bản
1. Chèn (Insert)
void insert(TrieNode* root, const string& key) {
TrieNode* curr = root;
for (char ch : key) {
int index = ch - 'A'; // Giả sử chỉ chữ hoa A-Z
if (!curr->children[index])
curr->children[index] = new TrieNode();
curr = curr->children[index];
}
curr->isEndOfWord = true;
}
2. Tìm kiếm (Search)
bool search(TrieNode* root, const string& key) {
TrieNode* curr = root;
for (char ch : key) {
int index = ch - 'A';
if (!curr->children[index]) return false;
curr = curr->children[index];
}
return curr->isEndOfWord;
}
3. Xóa (Delete) - Đệ quy
bool isEmpty(TrieNode* root) {
for (int i = 0; i < ALPHABET_SIZE; i++)
if (root->children[i]) return false;
return true;
}
TrieNode* deleteNode(TrieNode* root, const string& key, int depth = 0) {
if (!root) return nullptr;
if (depth == key.size()) {
if (root->isEndOfWord) root->isEndOfWord = false;
if (isEmpty(root)) {
delete root;
return nullptr;
}
return root;
}
int index = key[depth] - 'A';
root->children[index] = deleteNode(root->children[index], key, depth + 1);
// Nếu nút hiện tại không phải cuối từ và không có con → xóa
if (isEmpty(root) && !root->isEndOfWord) {
delete root;
return nullptr;
}
return root;
}
Chương trình hoàn chỉnh
#include <bits/stdc++.h>
using namespace std;
const int ALPHA_SIZE = 26; // Chỉ A-Z
struct TrieNode {
TrieNode* children[ALPHA_SIZE];
bool isEndOfWord;
TrieNode() : isEndOfWord(false) {
fill(begin(children), end(children), nullptr);
}
};
TrieNode* createNode() { return new TrieNode(); }
void insert(TrieNode* root, const string& key) {
TrieNode* curr = root;
for (char ch : key) {
int idx = ch - 'A';
if (!curr->children[idx]) curr->children[idx] = createNode();
curr = curr->children[idx];
}
curr->isEndOfWord = true;
}
bool search(TrieNode* root, const string& key) {
TrieNode* curr = root;
for (char ch : key) {
int idx = ch - 'A';
if (!curr->children[idx]) return false;
curr = curr->children[idx];
}
return curr && curr->isEndOfWord;
}
bool isEmpty(TrieNode* root) {
for (int i = 0; i < ALPHA_SIZE; i++)
if (root->children[i]) return false;
return true;
}
TrieNode* deleteNode(TrieNode* root, const string& key, int depth = 0) {
if (!root) return nullptr;
if (depth == key.size()) {
if (root->isEndOfWord) root->isEndOfWord = false;
if (isEmpty(root)) { delete root; return nullptr; }
return root;
}
int idx = key[depth] - 'A';
root->children[idx] = deleteNode(root->children[idx], key, depth + 1);
if (isEmpty(root) && !root->isEndOfWord) { delete root; return nullptr; }
return root;
}
int main() {
string keys[] = {"HELLOWORLD", "HI", "BYE", "THE", "THENA"};
TrieNode* root = createNode();
for (auto& k : keys) insert(root, k);
cout << (search(root, "HELLOWORLD") ? "Tim thay" : "Khong tim thay") << " HELLOWORLD\n";
cout << (search(root, "HE") ? "Tim thay" : "Khong tim thay") << " HE\n";
root = deleteNode(root, "THEN"); // Không tồn tại
cout << (search(root, "THE") ? "Tim thay" : "Khong tim thay") << " THE (sau khi xoa THEN)\n";
// TODO: Giải phóng bộ nhớ đệ quy toàn cây
return 0;
}
Kết quả
Tim thay HELLOWORLD Khong tim thay HE Tim thay THE (sau khi xoa THEN)
Mở rộng
- Hỗ trợ chữ thường:
tolower(ch) - 'a'hoặc mở rộngALPHA_SIZE = 52/128(ASCII). - Dùng
unordered_mapcho alphabet lớn/thưa (tiết kiệm bộ nhớ). - Thêm
int counttại nút để đếm tần suất từ / hỗ trợ prefix count.
Trie là cấu trúc dữ liệu cốt lõi cho xử lý chuỗi, nền tảng của nhiều thuật toán nâng cao (Aho-Corasick, suffix tree...).