Trie(前缀树)
前缀树是指:
- 单个字符串中,字符从前到后加到一棵多叉树上。
- 字符放在路上,字节点上有专属的数据项(常见的是pass值和end值,pass表示有多少路径经过该节点,end表示有多少路径以该节点结尾)
- 所有样本都这样添加,如果没有路就新建,如果有路就复用
- 沿途节点的pass值增加1,每个字符串结束时来到的节点end值增加1
代码实现(vector和map)
- 使用vector:
vector适合字符串中字符单一的情况,比如字符全是小写字母等:
class TrieNode
{
public:
TrieNode()
{
pass = 0;
end = 0;
//假设字符都为小写字母
nexts.resize(26,nullptr);
}
void insert(string word)
{
if (word == "")return;
TrieNode* node = this;
node->pass++;
int path = 0;
for (int i = 0; i < word.size(); i++)
{
path = word[i] - 'a';
if (node->nexts[path] == nullptr)
{
node->nexts[path] = new TrieNode();
}
node = node->nexts[path];
node->pass++;
}
node->end++;
}
//所有加入的字符串中,有几个是以pre这个字符串作为前缀的
int prefixNumber(string pre)
{
if (pre == "") return 0;
TrieNode* node = this;
int path = 0;
for (int i = 0; i < pre.size(); i++)
{
path = pre[i] - 'a';
if (node->nexts[path] == nullptr)
{
return 0;
}
node = node->nexts[path];
}
return node->pass;
}
//所有加入的字符串中,是否有word
bool search(string word)
{
if (word == "") return false;
TrieNode* node = this;
int path = 0;
for (int i = 0; i < word.size(); i++)
{
path = word[i] - 'a';
if (node->nexts[path] == nullptr)
{
return false;
}
node = node->nexts[path];
}
return node->end>0;
}
//递归删除多余的字符串,防止内存泄漏
private:
void _deleteNextNode(string word,TrieNode*cur,int i)
{
if (i == word.size())return;
else
{
int path= word[i] - 'a';
_deleteNextNode(word, cur->nexts[path], i+1);
delete cur->nexts[path];
}
}
public:
//删除字符串
void deleteString(string word)
{
if (search(word) != 0)
{
TrieNode* node = this;
node->pass--;
int path = 0;
for (int i = 0; i < word.size(); i++)
{
path = word[i] - 'a';
if (--node->nexts[path]->pass==0)
{
_deleteNextNode(word, node->nexts[path], i);
node->nexts[path] = nullptr;
return;
}
node = node->nexts[path];
}
node->end--;
}
}
//析构函数,将new出来的空间全部释放掉
~TrieNode()
{
for (auto& e : this->nexts)
{
if (e != nullptr)
{
delete e;
}
}
}
private:
//pass:经过该节点的路径有几条
int pass;
//end:以该节点结尾的路径有几条
int end;
//下级的数组
vector<TrieNode*> nexts;
};
- map实现
map适合字符串中字符种类很多的情况
class TrieNodeMap
{
public:
TrieNodeMap()
{
pass = 0;
end = 0;
//假设字符都为小写字母
}
void insert(string word)
{
if (word == "")return;
TrieNodeMap* node = this;
node->pass++;
int path = 0;
for (int i = 0; i < word.size(); i++)
{
path = (int)word[i];
if (node->nexts.find(path) == node->nexts.end())
{
node->nexts[path] = new TrieNodeMap();
}
node = node->nexts[path];
node->pass++;
}
node->end++;
}
//所有加入的字符串中,有几个是以pre这个字符串作为前缀的
int prefixNumber(string pre)
{
if (pre == "") return 0;
TrieNodeMap* node = this;
int path = 0;
for (int i = 0; i < pre.size(); i++)
{
path = (int)pre[i];
if (node->nexts.find(path) == node->nexts.end())
{
return 0;
}
node = node->nexts[path];
}
return node->pass;
}
//所有加入的字符串中,是否有word
bool search(string word)
{
if (word == "") return false;
TrieNodeMap* node = this;
int path = 0;
for (int i = 0; i < word.size(); i++)
{
path = (int)word[i];
if (node->nexts.find(path) == node->nexts.end())
{
return false;
}
node = node->nexts[path];
}
return node->end > 0;
}
//递归删除多余的字符串,防止内存泄漏
private:
void _deleteNextNode(string word, TrieNodeMap* cur, int i)
{
if (i == word.size())return;
else
{
int path = (int)word[i];
_deleteNextNode(word, cur->nexts[path], i + 1);
delete cur->nexts[path];
cur->nexts.erase(path);
}
}
public:
//删除字符串
void deleteString(string word)
{
if (search(word) != 0)
{
TrieNodeMap* node = this;
node->pass--;
int path = 0;
for (int i = 0; i < word.size(); i++)
{
path = (int)word[i];
if (--node->nexts[path]->pass == 0)
{
_deleteNextNode(word, node->nexts[path], i);
node->nexts.erase(path);
return;
}
node = node->nexts[path];
}
node->end--;
}
}
public:
//析构函数,将new出来的空间全部释放掉
~TrieNodeMap()
{
for (auto& e : this->nexts)
{
if (e.second != nullptr)
{
delete e.second;
e.second = nullptr;
}
}
return;
}
private:
//pass:经过该节点的路径有几条
int pass;
//end:以该节点结尾的路径有几条
int end;
//下级的数组
map<int,TrieNodeMap*> nexts;
};