字典(trie)树的应用与实现

来源:互联网 发布:王菲 蔡依林 知乎 编辑:程序博客网 时间:2024/06/05 15:48

字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

常见的可以使用字典树解决的问题举例:

1、已知N个单词,对于每一个单词,判断它有没有出现过,如果出现了,求第一次出现在第几个位置。

2、已知n个由小写字母构成的平均长度为10的单词,判断其中是否存在某个串为另一个串的前缀子串。

3、有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M,返回频数最高的100个词。

4、1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。

5、一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词。

6、给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出。

        上面的问题都可以通过建一个字典树解决,将字符串分别插入到字典树中,插入的同时可以进行查询,这样就可以知道该字符串有没有出现过。字典树结点可以添加一些附加信息,比如该结点代表的字符出现的次数,该结点是不是某个字符串的结尾以及该字符串出现的次数等。另外,对字典树进行先序遍历可以输出字符串的排序。

Tire树的结构如下图所示:

每个节点代表一个字符,从根节点向下遍历可以得到所有字符串的前缀,遇到结尾字符(红色)就得到一个字符串。

下面是我写的字典树简单实现,该字典树不仅保存了每个字符出现的次数(成员变量_count),该字典树中字符串的出现次数(成员变量_isEnd)。在插入字符串的每个字符的时候需要更新_count,插入完一个字符串后更新_isEnd。最后,删除一个字符串需要注意的是先判断是否存在待删除的字符串,如果存在,那么应该从下而上判断和删除节点(如果节点的计数为1,那么就释放内存,否则减小_count)。

/*实现一个字典树*/#include <iostream>#include <cstring>#include <stack>struct TrieNode{enum { MAX = 26 };int _isEnd;//表示字符串的结尾,_isEnd可以表示字符出现次数int _count;//字符出现的次数TireNode* children[MAX];//指针数组TireNode() : _isEnd(0), _count(1){memset(children, NULL, sizeof(children));}};TrieNode* create(){return new TireNode;}void insert(TrieNode* root, const char* str){if (NULL == root || NULL == str || '\0' == *str){return;}TrieNode* p = root;while (*str != '\0'){if (NULL == p->children[*str - 'a']) //结点不存在{p->children[*str - 'a'] = new TrieNode;}else{p->children[*str - 'a']->_count++;//更新字符结点计数}p = p->children[*str - 'a'];++str;}p->_isEnd++;//插完一个字符串更新其出现的次数}//字典树root是否包含字符串str, 如果isPrefix == true,那么str是root中某个字符串的一个前缀bool contains(TrieNode* root, const char* str, bool& isPrefix){if (NULL == root || NULL == str){return false;}if ('\0' == *str){return true;}TrieNode* p = root;while (p != NULL && *str != '\0'){p = p->children[*str - 'a']; //p指向str的当前字符++str;//str跳到下一个字符}//当*str == '\0'的时候,p指向str的最后一个有效字符(不是'\0')isPrefix = false;if (p != NULL){isPrefix = true;if (p->_isEnd > 0)//字符串结束符标记{return true;}}return false;}//从字典树中删除字符串str,如果不存在就返回false,成功删除就返回truebool remove(TrieNode* root, const char* str){if (NULL == root || NULL == str){return false;}if ('\0' == *str){return true;}std::stack<TrieNode**> s;//结点只能从下向上删除,否则会破坏树结构,释放内存后要置NULL,故保存指针的地址!!!TrieNode* p = root;while (p != NULL && *str != '\0'){if ((p->children[*str - 'a']) != NULL){s.push(&(p->children[*str - 'a']));//压入指针地址,很重要!}p = p->children[*str - 'a'];++str;//str跳到下一个字符}if (p != NULL && p->_isEnd > 0) //存在字符串str{TrieNode** end = s.top();//代表待删除的字符串的最后一个字符的节点(*end)->_isEnd--;while (!s.empty()){TrieNode** toDelete = s.top();if ((*toDelete)->_count == 1)//计数器为1了释放内存,否则只更新计数器{delete *toDelete;*toDelete = NULL;}else{(*toDelete)->_count--;}s.pop();}}return false;//trie树中不存在str}void release(TrieNode* root){for (int i = 0; i < TrieNode::MAX; ++i){if (root->children[i] != NULL){release(root->children[i]);}}delete root;}


2 0
原创粉丝点击