Trie,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不只限于字符串),因此常常被搜索引擎系统用于文本词频统计。它的优势是:最大限度地减小无谓的字符串比较,查询效率比哈希表高。php
它有3个基本性质:html
在这个Trie结构中,保存了A、to、tea、ted、ten、i、in、inn这8个字符串,仅占用8个字节(不包括指针占用的空间)。数组
这是一个用于词频统计的程序范例,因使用了getline(3),因此须要glibc才能连接成功,没有glibc的话能够自行改写。代码由User:JohnBull捐献,听从GPL版权声明。数据结构
#include <stdio.h> #include <stdlib.h> #include <string.h> #define TREE_WIDTH 256 #define WORDLENMAX 128 struct trie_node_st { int count; struct trie_node_st *next[TREE_WIDTH]; }; static struct trie_node_st root={0, {NULL}}; static char *spaces=" \t\n/.\"\'()"; static int insert(const char *word) { int i; struct trie_node_st *curr, *newnode; if (word[0]=='\0') { return 0; } curr = &root; for (i=0; ; ++i) { if (curr->next[ word[i] ] == NULL) { newnode=(struct trie_node_st*)malloc(sizeof(struct trie_node_st)); memset(newnode, 0, sizeof(struct trie_node_st)); curr->next[ word[i] ] = newnode; } if (word[i] == '\0') { break; } curr = curr->next[ word[i] ]; } curr->count ++; return 0; } static void printword(const char *str, int n) { printf("%s\t%d\n", str, n); } static int do_travel(struct trie_node_st *rootp) { static char worddump[WORDLENMAX+1]; static int pos=0; int i; if (rootp == NULL) { return 0; } if (rootp->count) { worddump[pos]='\0'; printword(worddump, rootp->count); } for (i=0;i<TREE_WIDTH;++i) { worddump[pos++]=i; do_travel(rootp->next[i]); pos--; } return 0; } int main(void) { char *linebuf=NULL, *line, *word; size_t bufsize=0; int ret; while (1) { ret=getline(&linebuf, &bufsize, stdin); if (ret==-1) { break; } line=linebuf; while (1) { word = strsep(&line, spaces); if (word==NULL) { break; } if (word[0]=='\0') { continue; } insert(word); } } /* free(linebuf); */ do_travel(&root); exit(0); }
在给一个例子:数据结构和算法
#define MAX_NUM 26 enum NODE_TYPE{ //"COMPLETED" means a string is generated so far. COMPLETED, UNCOMPLETED }; struct Node { enum NODE_TYPE type; char ch; struct Node* child[MAX_NUM]; //26-tree->a, b ,c, .....z }; struct Node* ROOT; //tree root struct Node* createNewNode(char ch){ // create a new node struct Node *new_node = (struct Node*)malloc(sizeof(struct Node)); new_node->ch = ch; new_node->type == UNCOMPLETED; int i; for(i = 0; i < MAX_NUM; i++) new_node->child[i] = NULL; return new_node; } void initialization() { //intiazation: creat an empty tree, with only a ROOT ROOT = createNewNode(' '); } int charToindex(char ch) { //a "char" maps to an index<br> return ch - 'a'; } int find(const char chars[], int len) { struct Node* ptr = ROOT; int i = 0; while(i < len) { if(ptr->child[charToindex(chars[i])] == NULL) { break; } ptr = ptr->child[charToindex(chars[i])]; i++; } return (i == len) && (ptr->type == COMPLETED); } void insert(const char chars[], int len) { struct Node* ptr = ROOT; int i; for(i = 0; i < len; i++) { if(ptr->child[charToindex(chars[i])] == NULL) { ptr->child[charToindex(chars[i])] = createNewNode(chars[i]); } ptr = ptr->child[charToindex(chars[i])]; } ptr->type = COMPLETED; }
Trie树的基本实现ide
字母树的插入(Insert)、删除( Delete)和查找(Find)都很是简单,用一个一重循环便可,即第i 次循环找到前i 个字母所对应的子树,而后进行相应的操做。实现这棵字母树,咱们用最多见的数组保存(静态开辟内存)便可,固然也能够开动态的指针类型(动态开辟内存)。至于结点对儿子的指向,通常有三种方法:
一、对每一个结点开一个字母集大小的数组,对应的下标是儿子所表示的字母,内容则是这个儿子对应在大数组上的位置,即标号;
二、对每一个结点挂一个链表,按必定顺序记录每一个儿子是谁;
三、使用左儿子右兄弟表示法记录这棵树。
三种方法,各有特色。第一种易实现,但实际的空间要求较大;第二种,较易实现,空间要求相对较小,但比较费时;第三种,空间要求最小,但相对费时且不易写。
三、 Trie树的高级实现
能够采用双数组(Double-Array)实现。利用双数组能够大大减少内存使用量,具体实现细节见参考资料(5)(6)。
四、 Trie树的应用
Trie是一种很是简单高效的数据结构,但有大量的应用实例。
(1) 字符串检索
事先将已知的一些字符串(字典)的有关信息保存到trie树里,查找另一些未知字符串是否出现过或者出现频率。
举例:
@ 给出N 个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最先出现的顺序写出全部不在熟词表中的生词。
@ 给出一个词典,其中的单词为不良单词。单词均为小写字母。再给出一段文本,文本的每一行也由小写字母构成。判断文本中是否含有任何不良单词。例如,若rob是不良单词,那么文本problem含有不良单词。
(2)字符串最长公共前缀
Trie树利用多个字符串的公共前缀来节省存储空间,反之,当咱们把大量字符串存储到一棵trie树上时,咱们能够快速获得某些字符串的公共前缀。
举例:
@ 给出N 个小写英文字母串,以及Q 个询问,即询问某两个串的最长公共前缀的长度是多少?
解决方案:首先对全部的串创建其对应的字母树。此时发现,对于两个串的最长公共前缀的长度即它们所在结点的公共祖先个数,因而,问题就转化为了离线(Offline)的最近公共祖先(Least Common Ancestor,简称LCA)问题。
而最近公共祖先问题一样是一个经典问题,能够用下面几种方法:
1. 利用并查集(Disjoint Set),能够采用采用经典的Tarjan 算法;
2. 求出字母树的欧拉序列(Euler Sequence )后,就能够转为经典的最小值查询(Range Minimum Query,简称RMQ)问题了;
(关于并查集,Tarjan算法,RMQ问题,网上有不少资料。)
(3)排序
Trie树是一棵多叉树,只要先序遍历整棵树,输出相应的字符串即是按字典序排序的结果。
举例:
@ 给你N 个互不相同的仅由一个单词构成的英文名,让你将它们按字典序从小到大排序输出。
(4) 做为其余数据结构和算法的辅助结构
如后缀树,AC自动机等
五、 Trie树复杂度分析
(1) 插入、查找的时间复杂度均为O(N),其中N为字符串长度。
(2) 空间复杂度是26^n级别的,很是庞大(可采用双数组实现改善)。
六、 总结
Trie树是一种很是重要的数据结构,它在信息检索,字符串匹配等领域有普遍的应用,同时,它也是不少算法和复杂数据结构的基础,如后缀树,AC自动机等,所以,掌握Trie树这种数据结构,对于一名IT人员,显得很是基础且必要!
七、 参考资料
(1)wiki:http://en.wikipedia.org/wiki/Trie
(2) 博文《字典树的简介及实现》:
http://hi.baidu.com/luyade1987/blog/item/2667811631106657f2de320a.html
(3) 论文《浅析字母树在信息学竞赛中的应用》
(4) 论文《Trie图的构建、活用与改进》
(5) 博文《An Implementation of Double-Array Trie》:
http://linux.thai.net/~thep/datrie/datrie.html
(6) 论文《An Efficient Implementation of Trie Structures》:
7)刚刚分享了:"算法合集之《浅析字母树在信息学竞赛中的应用》.pdf" 下载连接:http://t.cn/zOlasAHhttp://t.cn/zOlasAQ