梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)
Trie(字典树/前缀树)是计算机科学中的一种特殊多叉树数据结构,核心优势是能高效处理字符串的插入、查询(完整单词/前缀)操作,时间复杂度仅与字符串长度相关(O(L))。Trie树通过共享字符串前缀节省存储空间,在自动补全、拼写检查、词典检索、IP路由等场景中广泛应用。本教程基于C++实现完整的Trie树,包含插入、查询单词、查询前缀、删除单词等核心功能,从核心原理、代码结构到实战使用,全面讲解Trie树的原理与实现。
Trie树的核心操作围绕「路径遍历」展开,所有操作均从根节点开始,按字符逐个遍历子节点:
从根节点开始,遍历单词的每个字符:
从根节点开始,遍历单词的每个字符:
递归遍历单词路径,从叶子节点回溯:
提供的代码是完整的Trie树实现,采用C++结构体封装,分为头文件(声明)和源文件(实现)两部分,核心结构如下:
| 模块 | 作用 | 关键结构/函数 |
|---|---|---|
| Trie节点结构体 | 封装节点属性 | TrieNode(子节点数组、is_end标记) |
| Trie树类 | 封装所有操作 | Trie(根节点、插入/查询/删除等方法) |
// Trie节点结构体
struct TrieNode {
TrieNode* children[26]; // 子节点数组:26个元素,对应a-z
bool is_end; // 标记是否是单词结尾
int count; // 扩展:记录单词出现次数(可选)
// 构造函数:初始化节点
TrieNode() : is_end(false), count(0) {
for (int i = 0; i < 26; ++i) {
children[i] = nullptr; // 所有子节点初始化为空
}
}
// 析构函数:递归释放子节点内存
~TrieNode() {
for (int i = 0; i < 26; ++i) {
if (children[i] != nullptr) {
delete children[i];
children[i] = nullptr;
}
}
}
};
// Trie树类
class Trie {
private:
TrieNode* root; // 根节点(空节点)
// 递归删除辅助函数
bool removeHelper(TrieNode* node, const string& word, int idx);
public:
// 构造函数:初始化根节点
Trie() {
root = new TrieNode();
}
// 析构函数:释放根节点(递归释放所有子节点)
~Trie() {
delete root;
root = nullptr;
}
// 核心操作
void insert(const string& word); // 插入单词
bool search(const string& word); // 查询单词是否存在
bool startsWith(const string& prefix); // 查询前缀是否存在
bool remove(const string& word); // 删除单词
int getCount(const string& word); // 获取单词出现次数(扩展)
void printAllWords(); // 打印所有单词(扩展)
void printAllWordsHelper(TrieNode* node, string& path); // 辅助打印
};
void Trie::insert(const string& word) {
TrieNode* curr = root;
for (char c : word) {
int idx = c - 'a'; // 字符转索引(a->0, b->1...z->25)
if (curr->children[idx] == nullptr) {
curr->children[idx] = new TrieNode(); // 创建新节点
}
curr = curr->children[idx]; // 移动到子节点
}
curr->is_end = true; // 标记单词结束
curr->count++; // 词频+1
}
查询流程:
bool Trie::search(const string& word) {
TrieNode* curr = root;
for (char c : word) {
int idx = c - 'a';
if (curr->children[idx] == nullptr) {
return false; // 路径中断,单词不存在
}
curr = curr->children[idx];
}
return curr->is_end; // 必须是完整单词
}
查询流程:
bool Trie::startsWith(const string& prefix) {
TrieNode* curr = root;
for (char c : prefix) {
int idx = c - 'a';
if (curr->children[idx] == nullptr) {
return false; // 前缀路径不存在
}
curr = curr->children[idx];
}
return true; // 前缀存在即可,无需检查是否是完整单词
}
删除流程(递归回溯):
// 递归删除辅助函数
bool Trie::removeHelper(TrieNode* node, const string& word, int idx) {
// 递归终止:处理到单词最后一个字符
if (idx == word.size()) {
// 不是单词结尾,说明单词不存在
if (!node->is_end) return false;
// 取消结尾标记,词频-1
node->is_end = false;
node->count--;
// 判断该节点是否可以删除(无任何子节点)
for (int i = 0; i < 26; ++i) {
if (node->children[i] != nullptr) return false;
}
return true;
}
int c_idx = word[idx] - 'a';
// 路径不存在,单词不存在
if (node->children[c_idx] == nullptr) return false;
// 递归删除子节点
bool can_delete = removeHelper(node->children[c_idx], word, idx + 1);
// 如果子节点可以删除,释放内存并置空
if (can_delete) {
delete node->children[c_idx];
node->children[c_idx] = nullptr;
// 判断当前节点是否可以删除(无结尾标记 + 无其他子节点)
if (!node->is_end) {
for (int i = 0; i < 26; ++i) {
if (node->children[i] != nullptr) return false;
}
return true;
}
}
return false;
}
// 对外删除接口
bool Trie::remove(const string& word) {
return removeHelper(root, word, 0);
}
#include "Trie.h"
#include <iostream>
#include <string>
int main() {
Trie trie;
// 1. 插入单词
trie.insert("apple");
trie.insert("app");
trie.insert("banana");
trie.insert("band");
trie.insert("apple"); // 重复插入,词频+1
// 2. 查询单词
cout << "查询 \"apple\": " << (trie.search("apple") ? "存在" : "不存在") << endl; // 存在
cout << "查询 \"app\": " << (trie.search("app") ? "存在" : "不存在") << endl; // 存在
cout << "查询 \"appl\": " << (trie.search("appl") ? "存在" : "不存在") << endl; // 不存在
cout << "apple 出现次数: " << trie.getCount("apple") << endl; // 2
// 3. 查询前缀
cout << "查询前缀 \"app\": " << (trie.startsWith("app") ? "存在" : "不存在") << endl; // 存在
cout << "查询前缀 \"ban\": " << (trie.startsWith("ban") ? "存在" : "不存在") << endl; // 存在
cout << "查询前缀 \"xyz\": " << (trie.startsWith("xyz") ? "存在" : "不存在") << endl; // 不存在
// 4. 打印所有单词
cout << "所有单词: ";
trie.printAllWords(); // 输出: app apple banana band
// 5. 删除单词
cout << "删除 \"app\": " << (trie.remove("app") ? "成功" : "失败") << endl; // 成功
cout << "删除后查询 \"app\": " << (trie.search("app") ? "存在" : "不存在") << endl; // 不存在
cout << "删除后查询前缀 \"app\": " << (trie.startsWith("app") ? "存在" : "不存在") << endl; // 存在(apple还在)
return 0;
}
查询 "apple": 存在
查询 "app": 存在
查询 "appl": 不存在
apple 出现次数: 2
查询前缀 "app": 存在
查询前缀 "ban": 存在
查询前缀 "xyz": 不存在
所有单词: app apple banana band
删除 "app": 成功
删除后查询 "app": 不存在
删除后查询前缀 "app": 存在
扩展TrieNode支持权重存储,实现带权重的单词插入/查询:
// 扩展TrieNode结构体(Entitys.h)
struct WeightedTrieNode {
WeightedTrieNode* children[26];
bool is_end;
int weight; // 单词权重
WeightedTrieNode() : is_end(false), weight(0) {
for (int i = 0; i < 26; ++i) {
children[i] = nullptr;
}
}
~WeightedTrieNode() {
for (int i = 0; i < 26; ++i) {
if (children[i] != nullptr) {
delete children[i];
}
}
}
};
// 带权重的Trie类(Trie.h)
class WeightedTrie {
private:
WeightedTrieNode* root;
public:
WeightedTrie() { root = new WeightedTrieNode(); }
~WeightedTrie() { delete root; }
// 插入单词并设置权重
void insert(const string& word, int weight) {
WeightedTrieNode* curr = root;
for (char c : word) {
int idx = c - 'a';
if (curr->children[idx] == nullptr) {
curr->children[idx] = new WeightedTrieNode();
}
curr = curr->children[idx];
}
curr->is_end = true;
curr->weight = weight;
}
// 查询单词权重
int getWeight(const string& word) {
WeightedTrieNode* curr = root;
for (char c : word) {
int idx = c - 'a';
if (curr->children[idx] == nullptr) {
return -1; // 单词不存在
}
curr = curr->children[idx];
}
return curr->is_end ? curr->weight : -1;
}
};
// 使用示例
int main() {
WeightedTrie wt;
wt.insert("apple", 10);
wt.insert("banana", 5);
cout << "apple 权重: " << wt.getWeight("apple") << endl; // 10
cout << "banana 权重: " << wt.getWeight("banana") << endl;// 5
cout << "orange 权重: " << wt.getWeight("orange") << endl;// -1
return 0;
}
apple 权重: 10
banana 权重: 5
orange 权重: -1
#ifndef ENTITYS_H_INCLUDED
#define ENTITYS_H_INCLUDED
#include <string>
#include <vector>
//************************************************************************************************************************************************************************
// 自定义类型
//************************************************************************************************************************************************************************
//========================================================================================================================================================================
// 学生结构体(Student)
//========================================================================================================================================================================
struct Student {
int id;// 学号
std::string name;// 姓名
std::string dob;// 出生日期
std::string sex;// 性别
std::string gender;// 民族
std::string address;// 地址
float score;// 入学总分
std::string school;// 学校
std::string team;// 班级
std::string status;// 状态
bool operator<(const Student& other) const { return id < other.id; }
bool operator>(const Student& other) const { return id > other.id; }
bool operator==(const Student& other) const { return id == other.id; }
bool operator!=(const Student& other) const { return id != other.id; }
friend std::ostream& operator<<(std::ostream& os, const Student& s) {
os << "[" << s.id<< ", " << s.name << ", " << s.dob << ", " << s.sex << ", " << s.gender << ", " << s.address << ", " << s.score<< ", " << s.school<< ", " << s.team<< ", " << s.status << "]";
return os;
}
};
#endif // ENTITYS_H_INCLUDED
#ifndef TRIE_H_INCLUDED
#define TRIE_H_INCLUDED
#include <iostream>
#include <string>
#include <vector>
//========================================================================================================================================================================
// Trie节点结构体(TrieNode)
//========================================================================================================================================================================
struct TrieNode {
TrieNode* children[26]; // 子节点数组:26个元素,对应a-z
bool is_end; // 标记是否是单词结尾
int count; // 单词出现次数
// 构造函数
TrieNode() : is_end(false), count(0) {
for (int i = 0; i < 26; ++i) {
children[i] = nullptr;
}
}
// 析构函数:递归释放子节点
~TrieNode() {
for (int i = 0; i < 26; ++i) {
if (children[i] != nullptr) {
delete children[i];
children[i] = nullptr;
}
}
}
};
//========================================================================================================================================================================
// 带权重Trie节点结构体(WeightedTrieNode)
//========================================================================================================================================================================
struct WeightedTrieNode {
WeightedTrieNode* children[26];
bool is_end;
int weight; // 单词权重
WeightedTrieNode() : is_end(false), weight(0) {
for (int i = 0; i < 26; ++i) {
children[i] = nullptr;
}
}
~WeightedTrieNode() {
for (int i = 0; i < 26; ++i) {
if (children[i] != nullptr) {
delete children[i];
}
}
}
};
//========================================================================================================================================================================
//
// Trie树类(Trie)
//
//========================================================================================================================================================================
class Trie {
private:
TrieNode* root; // 根节点
// 递归删除辅助函数
bool removeHelper(TrieNode* node, const std::string& word, int idx);
// 打印所有单词辅助函数
void printAllWordsHelper(TrieNode* node, std::string& path);
public:
// 构造函数
Trie();
// 析构函数
~Trie();
// 核心操作
void insert(const std::string& word); // 插入单词
bool search(const std::string& word); // 查询单词是否存在
bool startsWith(const std::string& prefix); // 查询前缀是否存在
bool remove(const std::string& word); // 删除单词
int getCount(const std::string& word); // 获取单词出现次数
void printAllWords(); // 打印所有单词
};
//========================================================================================================================================================================
//
// 带权重Trie树类(WeightedTrie)
//
//========================================================================================================================================================================
class WeightedTrie {
private:
WeightedTrieNode* root;
public:
// 构造函数
WeightedTrie();
// 析构函数
~WeightedTrie();
// 插入单词并设置权重
void insert(const std::string& word, int weight);
// 查询单词权重
int getWeight(const std::string& word);
// 查询单词是否存在
bool search(const std::string& word);
};
#endif // TRIE_H_INCLUDED
#include "Trie.h"
//========================================================================================================================================================================
//
// Trie树类实现
//
//========================================================================================================================================================================
// 构造函数
Trie::Trie() {
root = new TrieNode();
}
// 析构函数
Trie::~Trie() {
delete root;
root = nullptr;
}
// 插入单词
void Trie::insert(const std::string& word) {
TrieNode* curr = root;
for (char c : word) {
int idx = c - 'a';
if (curr->children[idx] == nullptr) {
curr->children[idx] = new TrieNode();
}
curr = curr->children[idx];
}
curr->is_end = true;
curr->count++;
}
// 查询单词是否存在
bool Trie::search(const std::string& word) {
TrieNode* curr = root;
for (char c : word) {
int idx = c - 'a';
if (curr->children[idx] == nullptr) {
return false;
}
curr = curr->children[idx];
}
return curr->is_end;
}
// 查询前缀是否存在
bool Trie::startsWith(const std::string& prefix) {
TrieNode* curr = root;
for (char c : prefix) {
int idx = c - 'a';
if (curr->children[idx] == nullptr) {
return false;
}
curr = curr->children[idx];
}
return true;
}
// 递归删除辅助函数
bool Trie::removeHelper(TrieNode* node, const std::string& word, int idx) {
if (idx == word.size()) {
if (!node->is_end) return false;
node->is_end = false;
node->count--;
for (int i = 0; i < 26; ++i) {
if (node->children[i] != nullptr) return false;
}
return true;
}
int c_idx = word[idx] - 'a';
if (node->children[c_idx] == nullptr) return false;
bool can_delete = removeHelper(node->children[c_idx], word, idx + 1);
if (can_delete) {
delete node->children[c_idx];
node->children[c_idx] = nullptr;
if (!node->is_end) {
for (int i = 0; i < 26; ++i) {
if (node->children[i] != nullptr) return false;
}
return true;
}
}
return false;
}
// 删除单词
bool Trie::remove(const std::string& word) {
return removeHelper(root, word, 0);
}
// 获取单词出现次数
int Trie::getCount(const std::string& word) {
TrieNode* curr = root;
for (char c : word) {
int idx = c - 'a';
if (curr->children[idx] == nullptr) {
return 0;
}
curr = curr->children[idx];
}
return curr->is_end ? curr->count : 0;
}
// 打印所有单词辅助函数
void Trie::printAllWordsHelper(TrieNode* node, std::string& path) {
if (node->is_end) {
std::cout << path << " ";
}
for (int i = 0; i < 26; ++i) {
if (node->children[i] != nullptr) {
path.push_back('a' + i);
printAllWordsHelper(node->children[i], path);
path.pop_back();
}
}
}
// 打印所有单词
void Trie::printAllWords() {
std::string path;
printAllWordsHelper(root, path);
std::cout << std::endl;
}
//========================================================================================================================================================================
//
// 带权重Trie树类实现
//
//========================================================================================================================================================================
// 构造函数
WeightedTrie::WeightedTrie() {
root = new WeightedTrieNode();
}
// 析构函数
WeightedTrie::~WeightedTrie() {
delete root;
}
// 插入单词并设置权重
void WeightedTrie::insert(const std::string& word, int weight) {
WeightedTrieNode* curr = root;
for (char c : word) {
int idx = c - 'a';
if (curr->children[idx] == nullptr) {
curr->children[idx] = new WeightedTrieNode();
}
curr = curr->children[idx];
}
curr->is_end = true;
curr->weight = weight;
}
// 查询单词权重
int WeightedTrie::getWeight(const std::string& word) {
WeightedTrieNode* curr = root;
for (char c : word) {
int idx = c - 'a';
if (curr->children[idx] == nullptr) {
return -1;
}
curr = curr->children[idx];
}
return curr->is_end ? curr->weight : -1;
}
// 查询单词是否存在
bool WeightedTrie::search(const std::string& word) {
WeightedTrieNode* curr = root;
for (char c : word) {
int idx = c - 'a';
if (curr->children[idx] == nullptr) {
return false;
}
curr = curr->children[idx];
}
return curr->is_end;
}
#include <iostream>
#include <string>
#include "Trie.h"
int main() {
// 基础Trie树测试
Trie trie;
// 1. 插入单词
trie.insert("apple");
trie.insert("app");
trie.insert("banana");
trie.insert("band");
trie.insert("apple"); // 重复插入
// 2. 查询单词
std::cout << "查询 \"apple\": " << (trie.search("apple") ? "存在" : "不存在") << std::endl;
std::cout << "查询 \"app\": " << (trie.search("app") ? "存在" : "不存在") << std::endl;
std::cout << "查询 \"appl\": " << (trie.search("appl") ? "存在" : "不存在") << std::endl;
std::cout << "apple 出现次数: " << trie.getCount("apple") << std::endl;
// 3. 查询前缀
std::cout << "查询前缀 \"app\": " << (trie.startsWith("app") ? "存在" : "不存在") << std::endl;
std::cout << "查询前缀 \"ban\": " << (trie.startsWith("ban") ? "存在" : "不存在") << std::endl;
std::cout << "查询前缀 \"xyz\": " << (trie.startsWith("xyz") ? "存在" : "不存在") << std::endl;
// 4. 打印所有单词
std::cout << "所有单词: ";
trie.printAllWords();
// 5. 删除单词
std::cout << "删除 \"app\": " << (trie.remove("app") ? "成功" : "失败") << std::endl;
std::cout << "删除后查询 \"app\": " << (trie.search("app") ? "存在" : "不存在") << std::endl;
std::cout << "删除后查询前缀 \"app\": " << (trie.startsWith("app") ? "存在" : "不存在") << std::endl;
// 带权重Trie树测试
WeightedTrie wt;
wt.insert("apple", 10);
wt.insert("banana", 5);
std::cout << "\n带权重Trie测试:" << std::endl;
std::cout << "apple 权重: " << wt.getWeight("apple") << std::endl;
std::cout << "banana 权重: " << wt.getWeight("banana") << std::endl;
std::cout << "orange 权重: " << wt.getWeight("orange") << std::endl;
return 0;
}
查询 "apple": 存在
查询 "app": 存在
查询 "appl": 不存在
apple 出现次数: 2
查询前缀 "app": 存在
查询前缀 "ban": 存在
查询前缀 "xyz": 不存在
所有单词: app apple banana band
删除 "app": 成功
删除后查询 "app": 不存在
删除后查询前缀 "app": 存在
带权重Trie测试:
apple 权重: 10
banana 权重: 5
orange 权重: -1
字符集约束:
内存管理:
性能优化:
Trie树是专为字符串前缀匹配设计的高效数据结构,核心是共享前缀路径和字符索引遍历。其插入/查询操作的时间复杂度仅与字符串长度相关,是哈希表、红黑树等结构无法替代的最优解。
常见坑点与避坑经验
本教程通过完整的代码实现和实战示例,覆盖了Trie树的核心原理、关键操作和扩展功能。掌握Trie树的设计思想,能高效解决字符串前缀匹配、自动补全、词典检索等实际问题,是算法设计和工程开发中的重要工具。