前缀树也叫字典树,是一种树性结构,核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。应用如下:
- 快速查找某一字符串是否存在,字典树的查询时间复杂度是O(logL),L是字符串的长度。
- 计算以某一字符串为前缀的字符串个数
- 词频统计
前缀树结点的定义如下:
class TrieNode {
public:
int pass;//表示字符串通过该结点的次数
int end;//以该结点为结尾的字符串个数
vector<TrieNode*>next;//用小写英文字符表示边
TrieNode() :pass(0), end(0) {
next.resize(26);//假设只存放小写英文字母,如果字符很多,可以用map<char,TrieNode*>
for (int i = 0; i < 26; i++) {//用字符与a的ASCLL的差值表示26个小写字母,a表示0,b表示1,……,25表示z
next[i] = nullptr;//空表示没有边
}
}
TrieNode(int pass,int end) :pass(pass), end(end) {
next.resize(26);
for (int i = 0; i < 26; i++) {
next[i] = nullptr;
}
}
};
- 添加操作:从根结点出发,先判断是否有
s[i] - 'a'
表示的边即是否为NULL,若有则cur往下走,cur= cur->next[s[i] - 'a']
,同时cur->pass++;
;若没有则创建一个结点然后cur往下走,cur->next[s[i] - 'a'] = new TrieNode(1,0);cur= cur->next[s[i] - 'a'];
,当cur到达字符串结束位置时,pass++,end++。 - 删除操作:先查询有无该字符串,若有则沿着字符串的方向作pass–,并加路径置空,为防止内存泄漏需要手动释放内存
- 搜索字符串:和添加类似,从根结点出发,先判断是否有
s[i] - 'a'
表示的边,若有则cur往下走,若没有直接返回false。到达结束位置判断end是否大于0,此时的end表示该字符串出现的次数。 - 计算以某一字符串为前缀的字符串的个数:和搜索字符串类似,当到达字符串结束位置时返回pass即为以某一字符串为前缀的字符串的个数
代码实现如下:
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
//字符表示边
class Node {
public:
int pass;//经过该结点的字符串数
int end;//以该结点结尾的字符串数
vector<Node*>path;
Node() {
pass = 0;
end = 0;
path.resize(26);//假设只有字符串只有小写英文字母,0-a,1-b
}
};
class Triel {
private:
Node* root;
public:
Triel() {
root = new Node();
}
//插入字符串
void insert(string word) {
if (word.size() == 0) {
return;
}
Node* cur = root;
for (int i = 0; i < word.size(); ++i) {
if (cur->path[word[i] - 'a'] == NULL) {
Node* newNode = new Node();
cur->path[word[i] - 'a'] = newNode;
}
cur->pass++;
cur = cur->path[word[i] - 'a'];
}
cur->pass++;
cur->end++;
}
//查找字符串出现的次数,-1表示为出现
int search(string word) {
Node* cur = root;
for (int i = 0; i < word.size(); ++i) {
if (cur->path[word[i] - 'a'] != NULL){
cur = cur->path[word[i] - 'a'];
}
else {
return -1;
}
}
return cur->end;
}
//查找以某字符串为前缀的个数,-1表示没有
int searchPrefix(string word) {
Node* cur = root;
for (int i = 0; i < word.size(); ++i) {
if (cur->path[word[i] - 'a'] != NULL) {
cur = cur->path[word[i] - 'a'];
}
else {
return -1;
}
}
return cur->pass;
}
void deleteWord(string word) {
if (search(word)==-1) {
return;
}
Node* cur = root;
root->pass--;
int size = word.size();
vector<Node*>deleteNodes;//内存释放
for (int i = 0; i < size; ++i) {
Node* next = cur->path[word[i] - 'a'];
next->pass--;
if (next->pass == 0) {
cur->path[word[i] - 'a'] = NULL;
deleteNodes.push_back(next);
}
cur = next;
}
cur->end--;
for (int i = 0; i < deleteNodes.size(); ++i) {
delete deleteNodes[i];
}
}
};
int main()
{
Triel* tree = new Triel();
tree->insert("abc");
tree->insert("ac");
tree->insert("bc");
tree->insert("bc");
cout << tree->search("bc");
tree->deleteWord("bc");
tree->deleteWord("bc");
tree->deleteWord("bc");
cout << tree->search("bc");
return 0;
}