一.基础知识
前缀树(字典树),是一种多叉树。一般用于查找和存储字符串,这种数据结构可以很快查询到一个字符串出现过几次,一个前缀出现过几次。
例如:字符串"aa", "ab","aab", "aac", "bc"组成的前缀树如下图所示。
多叉树的每条边代表出现的字符,节点中可以根据所要实现的功能加入不同的数据。如上图所示,若有一条边经过一个节点,则该节点p值+1,若有一条路径以此节点结束,则该节点e值+1。
二.前缀树的基本原理
插入时:
开始时,前缀树中仅有root节点。此时,root节点中p值为0,e值为0。
现在插入"aa"
每当插入一个新字符串时,root节点的p值都应当++。
来到首个字符'a',当发现字符'a'没有对应的边相连时,则创建一个新节点。并且来到新创建的节点,将该节点p值+1
之后来到第二个字符'a',发现字符'a'没有对应的边相连,继续创建一个新节点。来到新创建的节点,将该节点p值+1,由于该字符为字符串的最后一个字符,所以e值+1。如下图所示:
查找指定字符时:
按照对应路径向下寻找,若中途发现对应路径节点为空,则没有该字符串。
若没有发生上述情况,则找到对应节点,该节点e值为给定字符串个数。
查找指定前缀时:
按照对应路径向下寻找,若中途发现对应路径节点为空,则没有该字符串。
若没有发生上述情况,则找到对应节点,该节点p值为给定字符串前缀个数。
三.实现
#include <unordered_map>
struct Node {
int pass;
int end;
std::unordered_map<int, Node *> next;
Node() {
pass = 0;
end = 0;
}
};
class Trie {
public:
Trie();
~Trie();
// 前缀树插入
void insert_trie(std::string s);
// 前缀树删除
void delete_trie(std::string s);
// 前缀树搜索(字符串出现次数)
int search_trie(std::string s);
// 前缀树搜索(字符串作为前缀次数)
int prefix_trie(std::string s);
private:
// 前缀树根节点
struct Node *root;
};
#endif //TRIE__TRIE_H_
#include "Trie.h"
Trie::Trie() {
root = new Node;
}
Trie::~Trie() {
delete root;
}
void Trie::insert_trie(std::string s) {
if (s.empty()) {
return;
}
Node *node = root;
node->pass++;
for (int i = 0; i < s.size(); i++) {
int index = s[i];
if (node->next.find(index) == node->next.end()) {
node->next[s[i]] = new Node;
}
node = node->next[s[i]];
node->pass++;
}
node->end++;
}
void Trie::delete_trie(std::string s) {
Node *node = root;
node->pass--;
if (search_trie(s)) {
for (int i = 0; i < s.size(); i++) {
if (--node->next[s[i]]->pass == 0) {
node->next.erase(s[i]);
return;
}
node = node->next[s[i]];
}
node->end--;
}
}
int Trie::search_trie(std::string s) {
Node *node = root;
for (int i = 0; i < s.size(); i++) {
if (node->next.find(s[i]) == node->next.end()) {
return 0;
}
node = node->next[s[i]];
}
return node->end;
}
int Trie::prefix_trie(std::string s) {
Node *node = root;
for (int i = 0; i < s.size(); i++) {
if (node->next.find(s[i]) == node->next.end()) {
return 0;
}
node = node->next[s[i]];
}
return node->pass;
}