概述
本篇文章可以用来练习C++,可以学习以下知识:
- C++编程规范
- C++基本语法
- 面向对象编程,类的定义和声明
- 运算符重载
- 掌握另一种数据结构,帮你在前进的道路上披荆斩棘
一张图介绍前缀树
比如现在有两个单词,"hello"和“help”, 只是为更加清楚的说明前缀树的作用,这里就不用“hello”和“world”了。
正如图中所看到的,前缀树中的所有单词公用相同的前缀节点,这样在匹配是也更加容易匹配。
实现一颗词典书
声明:
- 这里实现的单词书只支持小写(可以想办法改进,支持任意字符串)
- 只支持单词插入和查找(可以添加删除)
#include <iostream>
#include <string>
/**
* @brief TrieNode表示前缀树中的节点
*/
class TrieNode{
public:
TrieNode();
~TrieNode();
private:
TrieNode* child_[26]; //用于表示26个英文字母所在的节点
bool is_word_; //表示以当前字符结束的单词是否是之前插入的单词
public:
friend class Trie;
};
/**
* @brief 构造函数,初始化成员变量,初始化子节点指向空
* 将 is_word_ 初始化为 false
*/
TrieNode::TrieNode(): is_word_(false){
for(int i = 0; i < 26; i++){
child_[i] = nullptr;
}
}
/**
* @brief 析构函数,释放掉该节点
* 将 is_word_ 初始化为 false
*/
TrieNode::~TrieNode(){
for(int i = 0; i < 26; i++){
if(child_[i] != nullptr){
delete child_[i];
child_[i] = nullptr;
}
}
}
/**
* @brief Trie是字典树的定义,包含了插入和查找的操作
*/
class Trie{
public:
Trie();
~Trie();
private:
TrieNode* root;
public:
void insert(const char* s);
void insert(const std::string& s);
bool find(const char* s);
bool find(const std::string& s);
bool operator[](const char* s);
bool operator[](const std::string& s);
};
/**
* @brief 构造函数,初始化成员变量,将根节点置为空
*/
Trie::Trie(): root(nullptr){}
/**
* @brief 析构函数,删除根节点
*/
Trie::~Trie(){
std::cout << "~Trie()" << std::endl;
if(root != nullptr){
delete root;
root = nullptr;
}
}
/**
* @brief 插入函数,将单词插入到前缀树中
* @param s 是一个C类型的字符串的指针
* @return void
*/
void Trie::insert(const char *s){
if(root == nullptr){
root = new TrieNode();
}
TrieNode *p = root;
for(int i = 0; s[i] != '\0'; i++){
if(p->child_[s[i] - 'a'] == nullptr){
p->child_[s[i] - 'a'] = new TrieNode();
}
p = p->child_[s[i] - 'a'];
}
p->is_word_ = true;
}
/**
* @brief 和上面的插入函数功能相同,重载支持更多场景
* @param s C++类型的字符串
* @return void
*/
void Trie::insert(const std::string& s){
insert(s.c_str());
}
/**
* @brief 查找函数,查找前缀树中是否饱含某个单词
* @param s C类型的字符串的指针
* @return bool类型,如果存在s返回true,否则返回false
*/
bool Trie::find(const char* s){
TrieNode *p = root;
for(int i = 0; s[i] != '\0'; i++){
if(p->child_[s[i] - 'a'] == nullptr){
return false;
}
p = p->child_[s[i] - 'a'];
}
return p->is_word_;
}
/**
* @brief 和上面的find函数功能相同
* @param s C++类型的字符串
* @return bool类型,如果存在s返回true,否则返回false
*/
bool Trie::find(const std::string& s){
return find(s.c_str());
}
/**
* @brief 重载[]运算符,使查找操作更加简洁
* @param s C类型的字符串
* @return bool类型,如果存在s返回true,否则返回false
*/
bool Trie::operator[](const char* s){
return find(s);
}
/**
* @brief 重载[]运算符,使查找操作更加简洁
* @param s C类型的字符串
* @return bool类型,如果存在s返回true,否则返回false
*/
bool Trie::operator[](const std::string& s){
return find(s);
}
验证数据结构的正确性
int main(int argc, char **argv)
{
Trie t;
t.insert("helloworld");
t.insert("eric");
std::cout << t["helloworld"] << std::endl;
std::cout << t["eric"] << std::endl;
return 0;
}
个人能力有限,实现难免有疏忽的地方,如果发现还请指正。
在此基础上,还可以了解一下基数树,linux内核中很多地方都是用了该数据结构,字典树是一颗基为26的基数树,可以说,基数树是字典树的扩展。
基数树留给下一篇博客。