Trie树解析

来源:互联网 发布:mac 图片尺寸修改 编辑:程序博客网 时间:2024/05/18 12:42

前言

        我们都知道在用搜索引擎进行搜索时,当我们输入部分搜索关键词后,搜索引擎会自动给出一些相似的查询关键词,如在百度输入“Trie”之后,它会自动给出可能与Trie相关的查询。除此之外,我们知道在搜索已经的索引过程中,一个词的词频(TF)的作用很大,那么如何能够快速的统计出一个词在某一篇文档中出现的频率呢?

                    

        其实这些问题都可以用Trie树来解决。在计算机科学中,trie,又称前缀树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。trie树常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能(引用自维基百科)。下图示意了一个包含26个英文字母的Trie树的结构:

                                                      

         其中节点中标红是节点的标识域,根据具体的应用不同可以为不同的值。如当用于词频统计时,这个域可以是从根节点到该节点的路径所代表的单词出现的次数;当用于查找时,可以是标识从根节点到当前节点所代表的单词是否出现过。下面代码是基于词频统计的一个实现版本:

#define WORDLEN  128/*单词最大长度*/#define TREEWIDTH 256/*字母表大小*/struct Trie_node{int count;/*用于统计当前单词出现的频率*/struct Trie_node *next[TREEWIDTH];/*节点指针*/Trie_node():count(0){memset(next, 0, sizeof(Trie_node*)*TREEWIDTH);}};void insert(Trie_node* root, const char* word){Trie_node* t = root;while(*word != '\0'){if(t->next[*word] == NULL){Trie_node* new_node = new Trie_node;t->next[*word] = new_node;}t = t->next[*word];word++;}t->count++;}int search(Trie_node* root, const char* word){int ret = 0;if(root != NULL){Trie_node* t = root;while(*word != '\0'){if(t->next[*word] == NULL) break;t = t->next[*word];word++;}if(*word == '\0') ret = t->count;}return ret;}void deleteTrie(Trie_node *root){if(root == NULL) return;Trie_node* t = root;for(int i = 0; i < TREEWIDTH; i++){if(t->next[i] != NULL) deleteTrie(t->next[i]);}delete t;}static voidprintword(const char *str, int n){printf("%s\t%d\n", str, n);}void printWordF(Trie_node* root){if(root == NULL) return;static char worddump[WORDLEN + 1];static int pos = 0;if(root->count > 0){worddump[pos] = '\0';printword(worddump, root->count);}for(int i = 0; i < WORDLEN; i++){worddump[pos++] = (char)i;printWordF(root->next[i]);pos--;}}static inline bool childCount(Trie_node* node){int count = 0;for(int i = 0; i < TREEWIDTH; i++){if(node->next[i] != NULL) count++;}return count;}void deleteWord(Trie_node* root, const char *word){stack<Trie_node*> s;Trie_node* t = root;//搜索单词word在树中的路径while(*word != '\0' && t != NULL){t = t->next[*word];s.push(t);word++;}//根据word在树中的路径,删除属于word独享的一些边if(*word == '\0'){Trie_node* p = NULL;t->count = 0;while(!s.empty()){word--;t = s.top();s.pop();if(childCount(t) > 0) break;p = root;if(!s.empty()) p = s.top();delete t;p->next[*word] = NULL;}}}

总结

       trie树实际上是一个DFA(确定型自动机),通常可以用于词频统计和搜索提示。在实现时通常用转移矩阵表示。行表示状态,列表示输入字符,(行, 列)位置表示转移状态。这种方式的查询效率很高,但由于稀疏的现象严重,空间利用效率很低。也可以采用压缩的存储方式即链表来表示状态转移,但由于要线性查询,会造成效率低下,以上的实现便是基于链表的。

        要了解如何用非链表的形式来实现Trie树,可以参见http://linux.thai.net/~thep/datrie/datrie.html


原创粉丝点击