Trie树的常见应用大总结(面试+附代码实现)

(一)Trie的简介

Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树。他的核心思想是空间换时间,空间消耗大但是插入和查询有着很优秀的时间复杂度。

(二)Trie的定义

Trie树的键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀(prefix),从根节点到当前结点的路径上的所有字母组成当前位置的字符串,结点可以保存当前字符串、出现次数、指针数组(指向子树)以及是否是结尾标志等等。

[cpp] 
view plain
 copy

  1. typedef struct Trie_Node  
  2. {  
  3.     char count[15];      //单词前缀出现的次数   
  4.     struct Trie_Node* next[MAXN];    //指向各个子树的指针   
  5.     bool exist;    //标记结点处是否构成单词   
  6. }Trie;  

Trie树可以利用字符串的公共前缀来节约存储空间,如下图所示:

《Trie树的常见应用大总结(面试+附代码实现)》

它有3个基本性质:
(1) 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
(2) 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
(3) 每个节点的所有子节点包含的字符都不相同。
(三)Trie树的基本操作
(1)插入操作
按下标索引逐个插入字母,若当前字母存在则继续下一个,否则new出当前字母的结点,所以插入的时间复杂度只和字符串的长度n有关,为O(n)。

[cpp] 
view plain
 copy

  1. void Insert(Trie *root, char* s,char *add)  
  2. {  
  3.     Trie *p=root;  
  4.     while(*s!=‘\0’)  
  5.     {  
  6.         if(p->next[*s-‘a’]==NULL)  
  7.         {  
  8.             p->next[*s-‘a’]=createNode();  
  9.         }  
  10.         p=p->next[*s-‘a’];  
  11.        // p->count=add;  
  12.         ++s;   
  13.     }  
  14.     p->exist=true;   
  15.     strcpy(p->count,add);  
  16. }  

(2)查询操作

和插入操作相仿,若查询途中某一个结点并不存在,则直接就return返回。否则继续下去,当字符串结束时,trie树上也有结束标志,那么证明此字符串存在,return true;

[cpp] 
view plain
 copy

  1. int Search(Trie* root,const char* s)  
  2. {  
  3.     Trie *p=root;  
  4.     while(*s!=‘\0’)  
  5.     {  
  6.         p=p->next[*s-‘a’];  
  7.         if(p==NULL)  
  8.         return 0;  
  9.         ++s;  
  10.     }  
  11.     return p->count;  
  12. }  

(3)删除操作

一般来说,对Trie单个结点的删除操作不常见,所以我在这里也只提供递归删除整个树的操作

[cpp] 
view plain
 copy

  1. void del(Trie *root)  
  2. {  
  3.     for(int i=0;i<MAXN;i++)  
  4.     {  
  5.         if(root->next[i]!=NULL)  
  6.         {  
  7.            del(root->next[i]);     
  8.         }  
  9.     }  
  10. //  free(root);     
  11.    delete root;  
  12. }  

(4)遍历操作

如果我们想要将trie中的字符串排序输出,直接先序遍历即可。

[cpp] 
view plain
 copy

  1. void Print(Trie *root)  
  2. {  
  3.     Trie *p=root;  
  4.     if(p->exist)  
  5.     cout<<p->name<<“: “<<p->count<<endl;  
  6.     for(int i=0;i<26;i++)  
  7.     {  
  8.         if(p->next[i]!=NULL){  
  9.             Print(p->next[i]);  
  10.         }  
  11.     }   
  12. }   

(四)Trie树的具体应用

(1)统计前缀出现的次数

这是Trie最基本的应用,每个结点的字母使用count记录出现的次数即可。

这里提供一道题目,hdu 1251供大家练习。

[cpp] 
view plain
 copy

  1. //hdu 1251   统计前缀出现次数   
  2. #include <cstdio>  
  3. #include <iostream>  
  4. #include <string>  
  5. #include <cstring>  
  6. using namespace std;  
  7. const int MAXN=26;  
  8. typedef struct Trie_Node  
  9. {  
  10.     int count;      //单词前缀出现的次数   
  11.     struct Trie_Node* next[MAXN];    //指向各个子树的指针   
  12.     bool exist;    //标记结点处是否构成单词   
  13. }Trie;  
  14. Trie* createNode()  
  15. {  
  16.     //Trie* p =(Trie*)malloc(sizeof(Trie));  
  17.     Trie *p=new Trie;  
  18.     p->count=0;  
  19.     p->exist=false;  
  20.     memset(p->next,0,sizeof(p->next));  
  21.     return p;  
  22. }  
  23. void Insert(Trie *root, const char* s)  
  24. {  
  25.     Trie *p=root;  
  26.     while(*s!=‘\0’)  
  27.     {  
  28.         if(p->next[*s-‘a’]==NULL)  
  29.         {  
  30.             p->next[*s-‘a’]=createNode();  
  31.         }  
  32.         p=p->next[*s-‘a’];  
  33.         p->count+=1;  
  34.         ++s;   
  35.     }  
  36.     p->exist=true;   
  37. }  
  38.   
  39. int Search(Trie* root,const char* s)  
  40. {  
  41.     Trie *p=root;  
  42.     while(*s!=‘\0’)  
  43.     {  
  44.         p=p->next[*s-‘a’];  
  45.         if(p==NULL)  
  46.         return 0;  
  47.         ++s;  
  48.     }  
  49.     return p->count;  
  50. }  
  51.   
  52. void del(Trie *root)  
  53. {  
  54.     for(int i=0;i<MAXN;i++)  
  55.     {  
  56.         if(root->next[i]!=NULL)  
  57.         {  
  58.            del(root->next[i]);     
  59.         }  
  60.     }  
  61. //  free(root);     
  62.    delete root;  
  63. }  
  64. int main()  
  65. {  
  66.     char s[15];  
  67.     bool flag=false;  
  68.     Trie* root=createNode();  
  69.     while(gets(s))  
  70.     {  
  71.         if(flag)  
  72.         {  
  73.            int ans=Search(root,s);  
  74.            printf(“%d\n”,ans);    
  75.         }     
  76.         else  
  77.         {  
  78.             if(strlen(s)!=0)  
  79.             Insert(root,s);  
  80.         }  
  81.         if(strlen(s)==0)  
  82.         flag=true;    
  83.     }  
  84.     del(root);  
  85.     return 0;  
  86. }  

(2)翻译(密码,明文)

给定一组字符串s,k我们输入k则需要翻译成s,也就是说两者是映射关系。接下来我们给出一段话,让你翻译出正常的文章。用map固然简便,但是Trie的效率更加高。只需要在k的结尾结点出记录下s即可。

这里也提供一道题目,hdu 1075。(被注释的是我原来的程序,wa了,有大神看出来麻烦告诉我一下,谢谢)。

[cpp] 
view plain
 copy

  1. /* 
  2. //hdu 1075映射  
  3. #include <cstdio> 
  4. #include <iostream> 
  5. #include <string> 
  6. #include <cstring> 
  7. #include <stdlib.h> 
  8. using namespace std; 
  9. const int MAXN=26; 
  10. typedef struct Trie_Node 
  11. { 
  12.     char count[15];      //单词前缀出现的次数  
  13.     struct Trie_Node* next[MAXN];    //指向各个子树的指针  
  14.     bool exist;    //标记结点处是否构成单词  
  15. }Trie; 
  16. Trie* createNode() 
  17. { 
  18.     Trie* p =(Trie*)malloc(sizeof(Trie)); 
  19.     p->exist=false; 
  20.     memset(p->next,0,sizeof(p->next)); 
  21.     return p; 
  22. } 
  23. void Insert(Trie *root, char* s,char *add) 
  24. { 
  25.     Trie *p=root; 
  26.     while(*s!=’\0′) 
  27.     { 
  28.         if(p->next[*s-‘a’]==NULL) 
  29.         { 
  30.             p->next[*s-‘a’]=createNode(); 
  31.         } 
  32.         p=p->next[*s-‘a’]; 
  33.        // p->count=add; 
  34.         ++s;  
  35.     } 
  36.     p->exist=true;  
  37.     strcpy(p->count,add); 
  38. } 
  39.  
  40.  
  41.  
  42. void Search(Trie* root, const char* s) 
  43. { 
  44.     Trie *p=root; 
  45.     while(*s!=’\0′) 
  46.     { 
  47.         if(p->next[*s-‘a’]==NULL) 
  48.         {   
  49.             printf(“%s”,s); 
  50.             return ; 
  51.         } 
  52.      
  53.         p=p->next[*s-‘a’]; 
  54.         ++s; 
  55.     } 
  56.     if(p->exist) 
  57.     printf(“%s”,p->count); 
  58.     else 
  59.     printf(“%s”,s); 
  60. } 
  61.  
  62. void del(Trie *root) 
  63. { 
  64.     for(int i=0;i<MAXN;i++) 
  65.     { 
  66.         if(root->next[i]!=NULL) 
  67.         { 
  68.            del(root->next[i]);    
  69.         } 
  70.     } 
  71.     free(root);    
  72. } 
  73. int main() 
  74. { 
  75.     char text[3013],from[15],to[15]; 
  76.     Trie* root=createNode(); 
  77.     scanf(“%s”,from); 
  78.     while(scanf(“%s”,from),strcmp(from,”END”)) 
  79.     { 
  80.         scanf(“%s”,to); 
  81.         Insert(root,to,from); 
  82.     } 
  83.     scanf(“%s”,from); 
  84.     getchar(); 
  85.     while(gets(text),strcmp(text,”END”)) 
  86.     { 
  87.         int len=strlen(text); 
  88.         for(int i=0;i<len;i++) 
  89.         { 
  90.             if(islower(text[i])) 
  91.             { 
  92.                 int j=0; 
  93.                 char temp[15]; 
  94.                 memset(temp,’\0′,sizeof(temp)); 
  95.                 while(islower(text[i])) 
  96.                 temp[j++]=text[i++]; 
  97.                 Search(root,temp); 
  98.               
  99.             } 
  100.             if(!islower(text[i])) 
  101.             printf(“%c”,text[i]); 
  102.         } 
  103.         printf(“\n”); 
  104.     } 
  105.     return 0; 
  106. } 
  107. */  
  108.   
  109. #include<stdio.h>  
  110. #include<string.h>  
  111. #include<stdlib.h>  
  112. #include<string>  
  113.   
  114. using namespace std;  
  115.   
  116. struct node{  
  117.     char dic[15];  
  118.     node * next[26];  
  119.     bool flag;  
  120. }*root;  
  121.   
  122. node *build()  
  123. {  
  124.     node *p=(node *)malloc(sizeof(node));  
  125.     for(int i=0;i<26;i++)  
  126.         p->next[i]=NULL;  
  127.     p->flag=false;  
  128.     return p;  
  129. }  
  130.   
  131. void insert(char *earth,char *mars)  
  132. {  
  133.     int len=strlen(mars);  
  134.     node *p;  
  135.     p=root;  
  136.     for(int i=0;i<len;i++)  
  137.     {  
  138.         if(p->next[mars[i]-‘a’]==NULL)  
  139.             p->next[mars[i]-‘a’]=build();  
  140.         p=p->next[mars[i]-‘a’];  
  141.     }  
  142.     p->flag=true;  
  143.     strcpy(p->dic,earth);  
  144. }  
  145.   
  146. void query(char *earth)  
  147. {  
  148.     int len=strlen(earth);  
  149.     node *p;  
  150.     p=root;  
  151.     for(int i=0;i<len;i++)  
  152.     {  
  153.         if(p->next[earth[i]-‘a’]==NULL)  
  154.         {  
  155.             printf(“%s”,earth);  
  156.             return;  
  157.         }  
  158.         p=p->next[earth[i]-‘a’];  
  159.     }  
  160.     if(p->flag)  
  161.         printf(“%s”,p->dic);  
  162.     else  
  163.         printf(“%s”, earth);  
  164. }  
  165.   
  166.   
  167. int main()  
  168. {  
  169.     char earth[15],mars[15],ask[3010];  
  170.   
  171.   
  172.     scanf(“%s”,earth);  
  173.     root=build();  
  174.     while(scanf(“%s”,earth),strcmp(earth,“END”))  
  175.     {  
  176.         scanf(“%s”,mars);  
  177.         insert(earth,mars);  
  178.     }  
  179.   
  180.   
  181.     scanf(“%s”,earth);  
  182.     getchar();  
  183.     while(gets(ask),strcmp(ask,“END”))  
  184.     {  
  185.         int len=strlen(ask);  
  186.         for(int i=0;i<len;i++)  
  187.         {  
  188.             if(islower(ask[i]))  
  189.             {  
  190.                 int j=0;  
  191.                 memset(earth,‘\0’,sizeof(earth));  
  192.                 while(islower(ask[i]))  
  193.                     earth[j++]=ask[i++];  
  194.                 query(earth);  
  195.             }  
  196.             if(!islower(ask[i]))  
  197.                 printf(“%c”,ask[i]);  
  198.         }  
  199.         printf(“\n”);  
  200.     }  
  201.   
  202.     return 0;  
  203. }  

(3)实现搜索引擎的热门搜索排名

我的初步想法是和(1)类似,对(1)中的trie进行先序遍历,将字符串和出现次数存进一个结构体,最后对这个数组进行快速排序,时间复杂度为O(nlogn),看网上说可以利用分治+trie

+最小堆,我还没有仔细搞清楚,以后研究完在添加。

(4)输入自动补全

其实原理都差不多,把字符串结尾处的结点当作root,进行先序遍历,即可得出所有以输入的字符串为前缀的答案。

[cpp] 
view plain
 copy

  1. / 自动补全   
  2. #include <cstdio>  
  3. #include <iostream>  
  4. #include <string>  
  5. #include <cstring>  
  6. using namespace std;  
  7. const int MAXN=26;  
  8. typedef struct Trie_Node  
  9. {  
  10.     int count;      //单词出现的次数   
  11.     struct Trie_Node* next[MAXN];    //指向各个子树的指针   
  12.     bool exist;    //标记结点处是否构成单词   
  13.     char name[15];  
  14. }Trie;  
  15. Trie* createNode()  
  16. {  
  17.     Trie* p =(Trie*)malloc(sizeof(Trie));  
  18.     p->count=0;  
  19.     p->exist=false;  
  20.     memset(p->next,0,sizeof(p->next));  
  21.     return p;  
  22. }  
  23. void Insert(Trie *root,char* word)  
  24. {  
  25.     Trie *p=root;  
  26.     char *s=word;  
  27.     while(*s!=‘\0’)  
  28.     {  
  29.         if(p->next[*s-‘a’]==NULL)  
  30.         {  
  31.             p->next[*s-‘a’]=createNode();  
  32.         }  
  33.         p=p->next[*s-‘a’];  
  34.         ++s;   
  35.     }  
  36.      p->exist=true;   
  37.       p->count+=1;  
  38.    strcpy(p->name,word);  
  39. }  
  40.   
  41. Trie* Search(Trie* root, char* s)  
  42. {  
  43.     Trie *p=root;  
  44.     while(*s!=‘\0’)  
  45.     {  
  46.         p=p->next[*s-‘a’];  
  47.         if(p==NULL)  
  48.         return 0;  
  49.         ++s;  
  50.     }  
  51.     return p;  
  52. }  
  53.   
  54. void del(Trie *root)  
  55. {  
  56.     for(int i=0;i<MAXN;i++)  
  57.     {  
  58.         if(root->next[i]!=NULL)  
  59.         {  
  60.            del(root->next[i]);     
  61.         }  
  62.     }  
  63.     free(root);     
  64.   
  65. }  
  66. void Print(Trie *root)  
  67. {  
  68.     Trie *p=root;  
  69.     if(p->exist)  
  70.     cout<<p->name<<“: “<<p->count<<endl;  
  71.     for(int i=0;i<26;i++)  
  72.     {  
  73.         if(p->next[i]!=NULL){  
  74.             Print(p->next[i]);  
  75.         }  
  76.     }   
  77. }   
  78. int main()  
  79. {  
  80.     char s[15];  
  81.     Trie* root=createNode();  
  82.     for(int i=0;i<5;i++)  
  83.     {  
  84.         cin>>s;  
  85.         Insert(root,s);  
  86.     }  
  87.     while(cin>>s)  
  88.     {  
  89.         Trie *ans=Search(root,s);  
  90.         if(ans)  
  91.         Print(ans);  
  92.     }  
  93.     del(root);  
  94.     return 0;  
  95. }  

原文地址: http://blog.csdn.net/nk_test/article/details/47836119

    原文作者:Trie树
    原文地址: https://blog.csdn.net/omnispace/article/details/50837679
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞