Trie,又称字典树、单词查找树,是一种树形结构,用于保存大量的字符串。它的优点是:利用字符串的公共前缀来节约存储空间。
相对来说,Trie树是一种比较简单的数据结构.理解起来比较简单,正所谓简单的东西也得付出代价.故Trie树也有它的缺点,Trie树的内存消耗非常大.当然,或许用左儿子右兄弟的方法建树的话,可能会好点.
其基本性质可以归纳为:
1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3. 每个节点的所有子节点包含的字符都不相同。
其基本操作有:查找 插入和删除,当然删除操作比较少见.我在这里只是实现了对整个树的删除操作,至于单个word的删除操作也很简单.
搜索字典项目的方法为:
(1) 从根结点开始一次搜索;
(2) 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;
(3) 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。(4) 迭代过程……(5) 在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。其他操作类似处理
有一个存放英文单词的文本文件,现在需要知道某些给定的单词是否在该文件中存在,若存在,它又出现了多少次?
这样的问题解法有多种,普通青年直接暴力查找,稍文艺点的用map。顺序查找的话,每给定一个单词就得遍历整个字符串数组,时间开销实在太大;如果将所有的单词都存放在一个map中,每次查找的时间复杂度则降为O(log(n))。不得不说,对于一般的应用场景,map足够满足所有需求。
但对于这个问题,还有更好的方法。有一种数据结构似乎就是为解决此类问题而诞生的,它就是字典树。我们知道,英文单词有很多都有相同的前缀,对于相同的前缀,字典树只存储一次,而map则是有多少个单词就存储多少次,所以在这个问题中,字典树比map省空间;在字典树中查找字符串的时间复杂度只跟树的深度有关而跟究竟有多少个字符串无关,而树的深度只跟字符串的长度有关,超过30个拉丁字母的英文单词基本没有,所以在该问题中查找字符串的时间复杂度只有O(1),比map省时间。
所以对于这样一个特定的问题,字典树自然是最佳的选择
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=1075
一道变形的字典树问题,只需要在构建字典树的结尾加上一个对应的翻译字符串。
这里还要特别注意,结尾v一定最后赋值为-1,用来表示结束.
因为假如有abcde这个模式串的话,再出现ab的话就不知道了。
这道题目还可以用map做的,但是map用时比字典树大多了
#include <iostream> using namespace std; #define N 26 typedef struct Trie { Trie *next[N]; int v; char x[20]; //记录下单词的对应 }Trie; void Create_Trie(Trie **proot) { int i = 0; *proot = new Trie; for(i=0;i<N;i++) (*proot)->next[i] = NULL; } void insert(Trie *root,char *s,char *source) { int i,j = 0; int id; int lenth = strlen(s); Trie *p_current = root; Trie *p_new; for(i = 0;i< lenth;i++) { id = s[i] - 'a'; if(!p_current->next[id]) { p_new = new Trie; for(j = 0;j < N;j++) p_new->next[j] = NULL; p_current->next[id] = p_new; p_new->v =1; p_current = p_current->next[id]; //继续下一个新的开始 } else { p_current->next[id]->v++; p_current = p_current->next[id]; } } p_current->v = -1; //表示一个结束,若有后缀则会一直递推下去 strcpy(p_current->x,source); } bool find(Trie *root ,char *s) { Trie *p = root; int id; int length = strlen(s); for(int i =0; i<length;i++) { id = s[i] - 'a'; p = p->next[id]; if(p== NULL) //若为空,则找不到对应的字母 return false; } if(p->v == -1) { printf("%s",p->x); return true; } return false; } void destroy_Trie(Trie *proot) //递归释放空间 ,在用指针就可释放,但是函数申请必须传递指针的指针 { if(!proot) return; int i =0; for(i =0;i<N;i++) if(proot->next[i]) { destroy_Trie(proot->next[i]); } delete proot; proot = NULL ; } int main() { int i ,j; char xh[20]; char mar[20]; char a[4000]; Trie *root; Create_Trie(&root); scanf("%s",xh); while (scanf("%s",xh) && strcmp(xh,"END")) { scanf("%s",mar); insert(root,mar,xh); } scanf("%s",xh); getchar(); while (gets(a)&& strcmp(a,"END")) { int len = strlen(a); for(i =0 ;i< len ;i++) { while (!isalpha(a[i])) { if(i >=len) break; printf("%c",a[i]); i++; } int k =0; while(isalpha(a[i])) { xh[k] = a[i]; k++; i++; } i--; //避免在for循环重复+1 xh[k] = 0; if(find(root,xh) == false) printf("%s",xh); } printf("\n"); } destroy_Trie(root); return 0; }