在计算机科学中,前缀树又称字典树(tire—源自于retrieval),是一种有序树。它用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
举例说明:一个保存了8个键的 tire 树结构,“a”、 “ab”、“abc”、“to”、“tea”、“ten”、“ted”、“B”,这8个键所形成的字典树如下图所示:
前缀树的3个基本性质:
1、根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2、从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3、每个节点的所有子节点包含的字符都不相同。
tire 树的应用:
常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。
c++ 实现 tire 树结构:
1、树结构的定义
const int MaxBranchNum = 26;
class TrieNode{
public:
string word;
int path;
int End;
TrieNode* nexts[MaxBranchNum];
TrieNode()
{
word = "";
path = 0;
End = 0;
memset(nexts,NULL,sizeof(TrieNode*) * MaxBranchNum);
}
}
其中,MaxBranchNum 表示 trie 树的最大分支数量(这里设置为26,如果有需要可以扩展),path 表示以当前单词结尾的单词数量,用以统计该字符串作为前缀的字符串的个数;End 表示该节点之前的字符串为前缀的单词数量。
2、trie 树的创建
class TrieTree{
private:
TrieNode *root;
public:
TrieTree();
void insert(string str);
int search(string str);
void Delete(string str);
void destory(TrieNode* root);
void printPre(string str);
void Print(TrieNode* root);
int prefixNumbers(string str);
};
TrieTree::TrieTree()
{
root = new TrieNode();
}
其中,void insert(string str) 该函数为插入字符串 str;
int search(string str) 该函数为查询字符串str是否出现过,并返回作为前缀几次;
void Delete(string str) 该函数为删除字符串str;
void destory(TrieNode* root) 该函数为销毁 trie 树;
void printPre(string str) 该函数为打印以str作为前缀的单词;
void Print(TrieNode* root) 该函数为按照字典顺序输出以root为根的所有单词;
int prefixNumbers(string str) 该函数为返回以str为前缀的单词的个数。每个函数具体功能实现如下:
- 插入字符串 str
void TrieTree::insert(string str)
{
if(str == "")
return ;
char buf[str.size()];
strcpy(buf, str.c_str());
TrieNode* node = root;
int index = 0;
for(int i=0; i<strlen(buf); i++)
{
index = buf[i] - 'a';
if(node->nexts[index] == nullptr)
{
node->nexts[index] = new TrieNode();
}
node = node->nexts[index];
node->path++;//有一条路径划过这个节点
}
node->End++;
node->word = str;
}
- 查询字符串str是否出现过
int TrieTree::search(string str)
{
if(str == "")
return 0;
char buf[str.size()];
strcpy(buf, str.c_str());
TrieNode* node = root;
int index = 0;
for(int i=0;i<strlen(buf);i++)
{
index = buf[i] - 'a';
if(node->nexts[index] == nullptr)
{
return 0;
}
node = node->nexts[index];
}
if(node != nullptr)
{
return node->End;
}else
{
return 0;
}
}
- 删除字符串str
void TrieTree::Delete(string str)
{
if(str == "")
return ;
char buf[str.size()];
strcpy(buf, str.c_str());
TrieNode* node = root;
TrieNode* tmp;
int index = 0;
for(int i = 0 ; i<str.size();i++)
{
index = buf[i] - 'a';
tmp = node->nexts[index];
if(--node->nexts[index]->path == 0)
{
delete node->nexts[index];
}
node = tmp;
}
node->End--;
}
- 销毁 trie 树
void TrieTree::destory(TrieNode* root)
{
if(root == nullptr)
return ;
for(int i=0;i<MaxBranchNum;i++)
{
destory(root->nexts[i]);
}
delete root;
root = nullptr;
}
- 打印以str作为前缀的单词
void TrieTree::printPre(string str)
{
if(str == "")
return ;
char buf[str.size()];
strcpy(buf, str.c_str());
TrieNode* node = root;
int index = 0;
for(int i=0;i<strlen(buf);i++)
{
index = buf[i] - 'a';
if(node->nexts[index] == nullptr)
{
return ;
}
node = node->nexts[index];
}
Print(node);
}
- 按照字典顺序输出以root为根的所有单词
void TrieTree::Print(TrieNode* node)
{
if(node == nullptr)
return ;
if(node->word != "")
{
cout<<node->word<<" "<<node->path<<endl;
}
for(int i = 0;i<MaxBranchNum;i++)
{
Print(node->nexts[i]);
}
}
- 返回以str为前缀的单词的个数
int TrieTree::prefixNumbers(string str)
{
if(str == "")
return 0;
char buf[str.size()];
strcpy(buf, str.c_str());
TrieNode* node = root;
int index = 0;
for(int i=0;i<strlen(buf);i++)
{
index = buf[i] - 'a';
if(node->nexts[index] == nullptr)
{
return 0;
}
node = node->nexts[index];
}
return node->path;
}