1、什么是Trie树(参考)
Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
字典树3个基本性质:
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符都不相同。
假设有b,abc,abd,bcd,abcd,efg,hii 这6个单词,我们构建的树就是如下图这样的:
如上图所示,对于每一个节点,从根遍历到他的过程就是一个单词,如果这个节点被标记为红色,就表示这个单词存在,否则不存在。那么,对于一个单词,我只要顺着他从根走到对应的节点,再看这个节点是否被标记为红色就可以知道它是否出现过了。把这个节点标记为红色,就相当于插入了这个单词。这样一来我们查询和插入可以一起完成,所用时间仅仅为单词长度,在这一个样例,便是10。我们可以看到,trie树每一层的节点数是26^i级别的。所以为了节省空间。我们用动态链表,或者用数组来模拟动态。空间的花费,不会超过单词数×单词长度。
字典树的查询:
使用trie时,因为当查询如字符串abc是否为某个字符串的前缀时,显然以b,c,d....等不是以a开头的字符串就不用查找了。所以建立trie的复杂度为O(n*len),而建立+查询在trie中是可以同时执行的,建立的过程也就可以成为查询的过程,hash就不能实现这个功能。所以总的复杂度为O(n*len),实际查询的复杂度也只是O(len)。(说白了,就是Trie树的平均高度h为len,所以Trie树的查询复杂度为O(h)=O(len))。
字典树优缺点:
优点:
- 插入,查询,删除等操作复杂度为O(h),其中h为单词的长度。为什么会这么快呢,本质是空间换时间(空间复杂度为26的h次方),利用指针来避免做其他不必要的查找。(初始化的时间复杂度为 n*O(h),n为单词个数);
- 当储存大量单词或者说储存的单词有着共同前缀时节省了空间。(比如说用线性存储boy,boyfriend如用trie存储的差别);
缺点:
- 指针占用的空间,空间复杂度大。如果储存少量的单词,并不能节省空间。
字典树的应用:
- 字符串检索:事先将已知的一些字符串(字典)的有关信息保存到trie树里,查找另外一些未知字符串是否出现过或者出现频率。
- 字符串最长公共前缀(转化为寻找共同祖先问题)。
字典树和哈希表的比较(参考):
2、字典树的C++实现(参考)
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
const int Num = 26; //每个节点需要保存26个字母
struct TrieNode
{
bool Isword; //判断是否是单词
TrieNode* next[Num];
TrieNode() :Isword(false) //初始化
{
memset(next, NULL, sizeof(next));
}
};
class Trie
{
public:
Trie() { root = new TrieNode(); }
void insert(string word);
bool search(string word);
void deleteTrie(TrieNode* root);
private:
TrieNode* root;
};
void Trie::insert(string word)
{
TrieNode* location = root;
for (int i = 0; i < word.length();i++)
{
if (location->next[word[i] - 'a'] == nullptr)
{
TrieNode* temp = new TrieNode();
location->next[word[i] - 'a']=temp;
}
location = location->next[word[i] - 'a'];
}
location->Isword = true;
}
bool Trie::search(string word)
{
TrieNode* location = root;
//while (word&&location)//注意location不能为空
for (int i = 0; i < word.length()&&location;i++)
location = location->next[word[i] - 'a'];
return(location != NULL && location->Isword);
}
void Trie::deleteTrie(TrieNode* root)
{
for (int i = 0; i < Num; i++)
{
if (root->next[i] != NULL)
{
deleteTrie(root->next[i]);
}
}
delete root;
}
void main() //简单测试
{
Trie tree;
int n; //输入n个单词在字典树中
cin >> n;
while (n--)
{
string s;
cin >> s;
tree.insert(s);
}
string input;
cout << "输入要检查的单词" << endl;
cin >> input;
cout << boolalpha << tree.search(input) << endl;//查找是否存在是个单词
}