(一)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树可以利用字符串的公共前缀来节约存储空间,如下图所示:

它有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. int Search(Trie* root,const char* s)
  39. {
  40. Trie *p=root;
  41. while(*s!='\0')
  42. {
  43. p=p->next[*s-'a'];
  44. if(p==NULL)
  45. return 0;
  46. ++s;
  47. }
  48. return p->count;
  49. }
  50. void del(Trie *root)
  51. {
  52. for(int i=0;i<MAXN;i++)
  53. {
  54. if(root->next[i]!=NULL)
  55. {
  56. del(root->next[i]);
  57. }
  58. }
  59. //  free(root);
  60. delete root;
  61. }
  62. int main()
  63. {
  64. char s[15];
  65. bool flag=false;
  66. Trie* root=createNode();
  67. while(gets(s))
  68. {
  69. if(flag)
  70. {
  71. int ans=Search(root,s);
  72. printf("%d\n",ans);
  73. }
  74. else
  75. {
  76. if(strlen(s)!=0)
  77. Insert(root,s);
  78. }
  79. if(strlen(s)==0)
  80. flag=true;
  81. }
  82. del(root);
  83. return 0;
  84. }

(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. void Search(Trie* root, const char* s)
  40. {
  41. Trie *p=root;
  42. while(*s!='\0')
  43. {
  44. if(p->next[*s-'a']==NULL)
  45. {
  46. printf("%s",s);
  47. return ;
  48. }
  49. p=p->next[*s-'a'];
  50. ++s;
  51. }
  52. if(p->exist)
  53. printf("%s",p->count);
  54. else
  55. printf("%s",s);
  56. }
  57. void del(Trie *root)
  58. {
  59. for(int i=0;i<MAXN;i++)
  60. {
  61. if(root->next[i]!=NULL)
  62. {
  63. del(root->next[i]);
  64. }
  65. }
  66. free(root);
  67. }
  68. int main()
  69. {
  70. char text[3013],from[15],to[15];
  71. Trie* root=createNode();
  72. scanf("%s",from);
  73. while(scanf("%s",from),strcmp(from,"END"))
  74. {
  75. scanf("%s",to);
  76. Insert(root,to,from);
  77. }
  78. scanf("%s",from);
  79. getchar();
  80. while(gets(text),strcmp(text,"END"))
  81. {
  82. int len=strlen(text);
  83. for(int i=0;i<len;i++)
  84. {
  85. if(islower(text[i]))
  86. {
  87. int j=0;
  88. char temp[15];
  89. memset(temp,'\0',sizeof(temp));
  90. while(islower(text[i]))
  91. temp[j++]=text[i++];
  92. Search(root,temp);
  93. }
  94. if(!islower(text[i]))
  95. printf("%c",text[i]);
  96. }
  97. printf("\n");
  98. }
  99. return 0;
  100. }
  101. */
  102. #include<stdio.h>
  103. #include<string.h>
  104. #include<stdlib.h>
  105. #include<string>
  106. using namespace std;
  107. struct node{
  108. char dic[15];
  109. node * next[26];
  110. bool flag;
  111. }*root;
  112. node *build()
  113. {
  114. node *p=(node *)malloc(sizeof(node));
  115. for(int i=0;i<26;i++)
  116. p->next[i]=NULL;
  117. p->flag=false;
  118. return p;
  119. }
  120. void insert(char *earth,char *mars)
  121. {
  122. int len=strlen(mars);
  123. node *p;
  124. p=root;
  125. for(int i=0;i<len;i++)
  126. {
  127. if(p->next[mars[i]-'a']==NULL)
  128. p->next[mars[i]-'a']=build();
  129. p=p->next[mars[i]-'a'];
  130. }
  131. p->flag=true;
  132. strcpy(p->dic,earth);
  133. }
  134. void query(char *earth)
  135. {
  136. int len=strlen(earth);
  137. node *p;
  138. p=root;
  139. for(int i=0;i<len;i++)
  140. {
  141. if(p->next[earth[i]-'a']==NULL)
  142. {
  143. printf("%s",earth);
  144. return;
  145. }
  146. p=p->next[earth[i]-'a'];
  147. }
  148. if(p->flag)
  149. printf("%s",p->dic);
  150. else
  151. printf("%s", earth);
  152. }
  153. int main()
  154. {
  155. char earth[15],mars[15],ask[3010];
  156. scanf("%s",earth);
  157. root=build();
  158. while(scanf("%s",earth),strcmp(earth,"END"))
  159. {
  160. scanf("%s",mars);
  161. insert(earth,mars);
  162. }
  163. scanf("%s",earth);
  164. getchar();
  165. while(gets(ask),strcmp(ask,"END"))
  166. {
  167. int len=strlen(ask);
  168. for(int i=0;i<len;i++)
  169. {
  170. if(islower(ask[i]))
  171. {
  172. int j=0;
  173. memset(earth,'\0',sizeof(earth));
  174. while(islower(ask[i]))
  175. earth[j++]=ask[i++];
  176. query(earth);
  177. }
  178. if(!islower(ask[i]))
  179. printf("%c",ask[i]);
  180. }
  181. printf("\n");
  182. }
  183. return 0;
  184. }

(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. Trie* Search(Trie* root, char* s)
  41. {
  42. Trie *p=root;
  43. while(*s!='\0')
  44. {
  45. p=p->next[*s-'a'];
  46. if(p==NULL)
  47. return 0;
  48. ++s;
  49. }
  50. return p;
  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. }
  63. void Print(Trie *root)
  64. {
  65. Trie *p=root;
  66. if(p->exist)
  67. cout<<p->name<<": "<<p->count<<endl;
  68. for(int i=0;i<26;i++)
  69. {
  70. if(p->next[i]!=NULL){
  71. Print(p->next[i]);
  72. }
  73. }
  74. }
  75. int main()
  76. {
  77. char s[15];
  78. Trie* root=createNode();
  79. for(int i=0;i<5;i++)
  80. {
  81. cin>>s;
  82. Insert(root,s);
  83. }
  84. while(cin>>s)
  85. {
  86. Trie *ans=Search(root,s);
  87. if(ans)
  88. Print(ans);
  89. }
  90. del(root);
  91. return 0;
  92. }
原文地址: http://blog.csdn.net/nk_test/article/details/47836119

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

  1. php大文件读取excel分割,如何用phpspreadsheet来切割excel大文件(附代码)

    这篇文章给大家介绍的内容是关于如何用phpspreadsheet来切割excel大文件(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 背景: 利用phpspreadshee ...

  2. Python 初学者进阶的九大技能(附代码)

    授权自AI科技大本营(ID:rgznai100) 本文约4300字,建议阅读9分钟 本文为你介绍Python必须掌握的九大技能. 以下为译文: Python是一种很棒的语言,语法简单,无需在代码中搜索 ...

  3. 简单实现几种常见的前端效果,附代码!

    小伙伴们在参加web前端工程师面试的时候会遇到面试官问到几种比较常见的效果如何去实现,这里小千就来给大家整理了一下,快来看~ 1.防抖 2.节流 3.new 4.bind 5.deepCopy 6.柯 ...

  4. python 数据逐个验证_案例实战 | Python 实现 AB 测试中常见的分层抽样与假设检验 (附代码和数据集)...

    在这里插入图片描述 作者 l 萝卜 本文会将原理知识穿插于代码段中,相关代码和数据集可在公众号 " 数据分析与商业实践 " 后台回复 " AB测试 " 获取. ...

  5. Vega数据可视化工具——教你轻松玩转大数据可视化 | 附代码

    首发地址:https://yq.aliyun.com/articles/145929 目前我们处于信息时代,万物联网的时代已经到来.从我们的日常生活中可以发现,小到身边的智能家居,再到平时出行的共享单 ...

  6. 独家 | R语言中K邻近算法的初学者指南:从菜鸟到大神(附代码&链接)

    作者:Leihua Ye, UC Santa Barbara 翻译:陈超 校对:冯羽 本文约2300字,建议阅读10分钟 本文介绍了一种针对初学者的K临近算法在R语言中的实现方法. 本文呈现了一种在R ...

  7. k折交叉验证优缺点_R语言中K邻近算法的初学者指南:从菜鸟到大神(附代码&链接)...

    作者:Leihua Ye, UC Santa Barbara 翻译:陈超 校对:冯羽 本文约2300字,建议阅读10分钟 本文介绍了一种针对初学者的K临近算法在R语言中的实现方法. 本文呈现了一种在R ...

  8. 如何画出几种常见二分类损失函数(附代码)

    在二分类的监督学习中,支持向量机.逻辑斯谛回归与最大熵模型.提升方法各自使用合页损失函数.逻辑斯谛损失函数.指数损失函数,分别写为: 这 3 种损失函数都是 0-1 损失函数的上界,具有相似的形状.( ...

  9. Java中常见的30道例题(附代码)

    目录 第一题:判断数组中奇数偶数的个数并求和 第二题:判断三个数中的最大值 第三题:剔除某些数据并打印输出 第四题:输入0到7判断星期几 第五题:将一个数组倒序输出 第六题:输入一个数判断是否是素数 ...

最新文章

  1. jar java classpath_win7中java编程工具安装 java环境变量设置
  2. Pause/Resume Instance 操作详解 - 每天5分钟玩转 OpenStack(34)
  3. 50本精品前端开发书籍免费下载
  4. Java对象克隆方法(浅克隆、深克隆)
  5. 2018/7/13-纪中某C组题【jzoj3382,jzoj3383,jzoj3384,jzoj3385】
  6. SAP License:发票校验抬头税码选择清单
  7. python入门经典100例-Python3经典100例(含习题答案) DOC 清晰版
  8. java学生管理系统报告_java学生管理系统总结报告.doc
  9. 使用nginx + uwsgi部署自己的django项目
  10. 2021年广东专精特新中小企业补助及小巨人企业补贴
  11. 创新设计思维---自学报告
  12. Quill富文本编辑器-图片上传-可编辑图片大小、排版
  13. photoshop cs6 下载并安装教程
  14. 使用朴素贝叶斯对电影评论分类
  15. pytorch局部范围内禁用梯度计算,no_grad、enable_grad、set_grad_enabled使用举例
  16. 问题 D: 天神下凡
  17. emplace 和 emplace_back
  18. 刘强东:B2C电商的本质
  19. 计算机网络传播时延公式,计算机网络时延的四种时延类型都有哪些
  20. 用16进制表示的颜色代码表

热门文章

  1. Sql Server定时自动备份数据库
  2. javascript:重新加载js文件
  3. Thread concepts
  4. 监测磁盘文件是否被修改程序
  5. 不同版本GCC编译器之间的切换
  6. mount and fstab的使用(整理)
  7. 安装模块时提示Collecting package metadata (repodata.json): failed
  8. 【Leetcode】创建二叉树
  9. SEO全套精品教程价值300元[159课]
  10. 6 种激活函数核心知识点,请务必掌握!