字典树,又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。
字典树与字典很相似,当你要查一个单词是不是在字典树中,首先看单词的第一个字母是不是在字典的第一层,如果不在,说明字典树里没有该单词,如果在就在该字母的孩子节点里找是不是有单词的第二个字母,没有说明没有该单词,有的话用同样的方法继续查找.字典树不仅可以用来储存字母,也可以储存数字等其它数据。
好了,废话不多说,下面看其定义和操作。
数据结构的定义如下:
1 #define MAXN 10 2 3 typedef struct Trie 4 { 5 Trie *next[MAXN]; 6 int v; 7 }Trie; 8 9 Trie *root;
1、先说一下root。root是整棵字典树的根,其中不存放任何与输入的字符串有关的信息,它的子结点才是输入字符串的第一个字符--str[0]...
2、每个结点的最大分支数MAXN根据要求变化,如果输入的是数字(0~9)的组合,定义为10就ok,若是小写字母则定义为26,大小写字母定义为52...
3、其中的v比较灵活,用于保存一些信息,具体是什么信息根据题目具体要求具体变化,如:可以定义为以从root到此结点的结点组合为前缀的字符串的个数,可以用来记录此结点是否为某个字符串的尾结点...
4、对于root的定义一般也有两种写法:Trie root; 或者 Trie *root; 第一种一般用于一个题目只有一棵字典树的情况,第二种主要用于题目中需要动态建立多棵字典树进行处理的时候(便于清空已经使用过的Trie内存),在main()中要root = malloc(sizeof(Trie));
字典树建立过程
1、依次读取字符串的每个字符...
2、建树过程中,需要两个temp指针,*p 和 *q,*p 用于从根节点开始(Trie *p = root;若前面定义root时用的是第二种方式,则此处为Trie *p = &root;)依次往下找,每次都选择str[i]所在的子树,若str[i]已经存在则p=p->next[id],若不存在则用*q建立一个新结点,将其插到P->next[]中(若对v有操作,此时也同时进行)...
3、在读取字符串每个字符的for循环结束后,一棵其中只包含了一个字符串信息的字典树就建成了...
4、对每个要加入到树中的字符串依次调用此函数...
模版如下:
1 void createTrie(char *str) 2 { 3 int len = strlen(str); 4 Trie *p = root, *q; 5 for(int i=0; i<len; ++i) 6 { 7 int id = str[i]-'0'; 8 if(p->next[id] == NULL) 9 { 10 q = (Trie *)malloc(sizeof(Trie)); 11 q->v = 0; //对v的初始化 12 for(int j=0; j<MAX; ++j) 13 q->next[j] = NULL; 14 p->next[id] = q; 15 p = p->next[id]; 16 } 17 else 18 {19 p = p->next[id]; 20 } 21 } 22 /*code*/ 23 }
1、若是字母的字典树,第7行代码改为 int id = str[i] - 'a';
2、其中对于v的操作具体变化
查找过程
(1) 每次从根结点开始一次搜索;
(2) 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;
(3) 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。
(4) 迭代过程……
(5) 在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。
模版如下:
1 int findTrie(char *str) 2 { 3 Trie *p = &root; 4 for (int i = 0; i < strlen(str); ++i) 5 { 6 int id = str[i] - 'a'; 7 if (p->next[id] == NULL) 8 { 9 return 0; 10 }else 11 { 12 p = p->next[id]; 13 } 14 } 15 return p->v; 16 }
这个是用于处理以str为前缀的字符串的数目的...
删除--释放内存
我们可以看到,其实字典树的空间开销是比较大的,尤其是动态建立了多棵字典树的时候,所以在题目上的内存有限制的情况下,我们有必要在使用完一棵字典树时将其内存释放,代码如下:
1 int dealTrie(Trie* T) 2 { 3 int i; 4 if(T==NULL) 5 return 0; 6 for(i=0;i<MAX;i++) 7 { 8 if(T->next[i]!=NULL) 9 deal(T->next[i]); 10 } 11 free(T); 12 return 0; 13 }
到此一棵字典树的基本操作就完成了。
1、HDOJ 1251 统计难题
http://acm.hdu.edu.cn/showproblem.php?pid=1251
2、HDOJ 1671 Phone List
http://acm.hdu.edu.cn/showproblem.php?pid=1671
3、想深入研究的童鞋可以看看这篇字典树的文章 PDF : 算法合集之《浅析字母树在信息学竞赛中的应用》
http://pan.baidu.com/s/16dkSY
这是上面两题的AC代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #define MAXL 15 5 #define MAXN 30 6 7 typedef struct Trie 8 { 9 Trie *next[MAXN]; 10 int v; 11 }Trie; 12 13 Trie root; 14 15 int createTrie(char *str) 16 { 17 Trie *p = &root, *q; 18 for (int i = 0; i < strlen(str); ++i) 19 { 20 int id = str[i] - 'a'; 21 if (p->next[id] == NULL) 22 { 23 q = (Trie*)malloc(sizeof(Trie)); 24 q->v = 1; 25 for (int j = 0; j < MAXN; ++j) 26 { 27 q->next[j] = NULL; 28 } 29 p->next[id] = q; 30 p = p->next[id]; 31 }else 32 { 33 p->next[id]->v++; 34 p = p->next[id]; 35 } 36 } 37 return 0; 38 } 39 40 int findTrie(char *str) 41 { 42 Trie *p = &root; 43 for (int i = 0; i < strlen(str); ++i) 44 { 45 int id = str[i] - 'a'; 46 if (p->next[id] == NULL) 47 { 48 return 0; 49 }else 50 { 51 p = p->next[id]; 52 } 53 } 54 return p->v; 55 } 56 57 int main() 58 { 59 char line[MAXL]; 60 while(gets(line) && line[0] != '