一、知识简介  
      最近在看字符串算法了,其中字典树、AC自动机和后缀树的应用是最广泛的了,下面将会重点介绍下这几个算法的应用。
      字典树(Trie)可以保存一些字符串->值的对应关系。基本上,它跟 Java 的 HashMap 功能相同,都是 key-value 映射,只不过 Trie 的 key 只能是字符串。
  Trie 的强大之处就在于它的时间复杂度。它的插入和查询时间复杂度都为 O(k) ,其中 k 为 key 的长度,与 Trie 中保存了多少个元素无关。Hash 表号称是 O(1) 的,但在计算 hash 的时候就肯定会是 O(k) ,而且还有碰撞之类的问题;Trie 的缺点是空间消耗很高。
  至于Trie树的实现,可以用数组,也可以用指针动态分配,我做题时为了方便就用了数组,静态分配空间。
      Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
      Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
Trie树的基本性质可以归纳为: 
(1)根节点不包含字符,除根节点以外每个节点只包含一个字符。
(2)从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。 
(3)每个节点的所有子节点包含的字符串不相同。
Trie树有一些特性:
1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3)每个节点的所有子节点包含的字符都不相同。
4)如果字符的种数为n,则每个结点的出度为n,这也是空间换时间的体现,浪费了很多的空间。
5)插入查找的复杂度为O(n),n为字符串长度。
基本思想(以字母树为例):
1、插入过程
对于一个单词,从根开始,沿着单词的各个字母所对应的树中的节点分支向下走,直到单词遍历完,将最后的节点标记为红色,表示该单词已插入Trie树。
2、查询过程
同样的,从根开始按照单词的字母顺序向下遍历trie树,一旦发现某个节点标记不存在或者单词遍历完成而最后的节点未标记为红色,则表示该单词不存在,若最后的节点标记为红色,表示该单词存在。

二、字典树的数据结构:
    利用串构建一个字典树,这个字典树保存了串的公共前缀信息,因此可以降低查询操作的复杂度。
    下面以英文单词构建的字典树为例,这棵Trie树中每个结点包括26个孩子结点,因为总共有26个英文字母(假设单词都是小写字母组成)。
    则可声明包含Trie树的结点信息的结构体:

  1. typedef struct Trie_node
  2. {
  3. int count;                    // 统计单词前缀出现的次数
  4. struct Trie_node* next[26];   // 指向各个子树的指针
  5. bool exist;                   // 标记该结点处是否构成单词
  6. }TrieNode , *Trie;

其中next是一个指针数组,存放着指向各个孩子结点的指针。
      如给出字符串"abc","ab","bd","dda",根据该字符串序列构建一棵Trie树。则构建的树如下:

Trie树的根结点不包含任何信息,第一个字符串为"abc",第一个字母为'a',因此根结点中数组next下标为'a'-97的值不为NULL,其他同理,构建的Trie树如图所示,红色结点表示在该处可以构成一个单词。很显然,如果要查找单词"abc"是否存在,查找长度则为O(len),len为要查找的字符串的长度。而若采用一般的逐个匹配查找,则查找长度为O(len*n),n为字符串的个数。显然基于Trie树的查找效率要高很多。
如上图中:Trie树中存在的就是abc、ab、bd、dda四个单词。在实际的问题中可以将标记颜色的标志位改为数量count等其他符合题目要求的变量。
已知n个由小写字母构成的平均长度为10的单词,判断其中是否存在某个串为另一个串的前缀子串。下面对比3种方法:

1、 最容易想到的:即从字符串集中从头往后搜,看每个字符串是否为字符串集中某个字符串的前缀,复杂度为O(n^2)。

2、 使用hash:我们用hash存下所有字符串的所有的前缀子串。建立存有子串hash的复杂度为O(n*len)。查询的复杂度为O(n)* O(1)= O(n)。

3、 使用Trie:因为当查询如字符串abc是否为某个字符串的前缀时,显然以b、c、d....等不是以a开头的字符串就不用查找了,这样迅速缩小查找的范围和提高查找的针对性。所以建立Trie的复杂度为O(n*len),而建立+查询在trie中是可以同时执行的,建立的过程也就可以成为查询的过程,hash就不能实现这个功能。所以总的复杂度为O(n*len),实际查询的复杂度只是O(len)。
三、Trie树的操作
    在Trie树中主要有3个操作,插入、查找和删除。一般情况下Trie树中很少存在删除单独某个结点的情况,因此只考虑删除整棵树。
1、插入
  假设存在字符串str,Trie树的根结点为root。i=0,p=root。
  1)取str[i],判断p->next[str[i]-97]是否为空,若为空,则建立结点temp,并将p->next[str[i]-97]指向temp,然后p指向temp;
   若不为空,则p=p->next[str[i]-97];
  2)i++,继续取str[i],循环1)中的操作,直到遇到结束符'\0',此时将当前结点p中的 exist置为true。
2、查找
  假设要查找的字符串为str,Trie树的根结点为root,i=0,p=root
  1)取str[i],判断判断p->next[str[i]-97]是否为空,若为空,则返回false;若不为空,则p=p->next[str[i]-97],继续取字符。
  2)重复1)中的操作直到遇到结束符'\0',若当前结点p不为空并且 exist 为true,则返回true,否则返回false。
3、删除
  删除可以以递归的形式进行删除。
前缀查询的典型应用:
http://acm.hdu.edu.cn/showproblem.php?pid=1251

  1. #include<iostream>
  2. #include<cstring>
  3. using namespace std;
  4. typedef struct Trie_node
  5. {
  6. int count;                    // 统计单词前缀出现的次数
  7. struct Trie_node* next[26];   // 指向各个子树的指针
  8. bool exist;                   // 标记该结点处是否构成单词
  9. }TrieNode , *Trie;
  10. TrieNode* createTrieNode()
  11. {
  12. TrieNode* node = (TrieNode *)malloc(sizeof(TrieNode));
  13. node->count = 0;
  14. node->exist = false;
  15. memset(node->next , 0 , sizeof(node->next));    // 初始化为空指针
  16. return node;
  17. }
  18. void Trie_insert(Trie root, char* word)
  19. {
  20. Trie node = root;
  21. char *p = word;
  22. int id;
  23. while( *p )
  24. {
  25. id = *p - 'a';
  26. if(node->next[id] == NULL)
  27. {
  28. node->next[id] = createTrieNode();
  29. }
  30. node = node->next[id];  // 每插入一步,相当于有一个新串经过,指针向下移动
  31. ++p;
  32. node->count += 1;      // 这行代码用于统计每个单词前缀出现的次数(也包括统计每个单词出现的次数)
  33. }
  34. node->exist = true;        // 单词结束的地方标记此处可以构成一个单词
  35. }
  36. int Trie_search(Trie root, char* word)
  37. {
  38. Trie node = root;
  39. char *p = word;
  40. int id;
  41. while( *p )
  42. {
  43. id = *p - 'a';
  44. node = node->next[id];
  45. ++p;
  46. if(node == NULL)
  47. return 0;
  48. }
  49. return node->count;
  50. }
  51. int main(void)
  52. {
  53. Trie root = createTrieNode();     // 初始化字典树的根节点
  54. char str[12] ;
  55. bool flag = false;
  56. while(gets(str))
  57. {
  58. if(flag)
  59. printf("%d\n",Trie_search(root , str));
  60. else
  61. {
  62. if(strlen(str) != 0)
  63. {
  64. Trie_insert(root , str);
  65. }
  66. else
  67. flag = true;
  68. }
  69. }
  70. return 0;
  71. }

字典树的查找
http://acm.hdu.edu.cn/showproblem.php?pid=1075

    1. #include<iostream>
    2. #include<cstring>
    3. using namespace std;
    4. typedef struct Trie_node
    5. {
    6. int count;                    // 统计单词前缀出现的次数
    7. struct Trie_node* next[26];   // 指向各个子树的指针
    8. bool exist;                   // 标记该结点处是否构成单词
    9. char trans[11];               // 翻译
    10. }TrieNode , *Trie;
    11. TrieNode* createTrieNode()
    12. {
    13. TrieNode* node = (TrieNode *)malloc(sizeof(TrieNode));
    14. node->count = 0;
    15. node->exist = false;
    16. memset(node->next , 0 , sizeof(node->next));    // 初始化为空指针
    17. return node;
    18. }
    19. void Trie_insert(Trie root, char* word , char* trans)
    20. {
    21. Trie node = root;
    22. char *p = word;
    23. int id;
    24. while( *p )
    25. {
    26. id = *p - 'a';
    27. if(node->next[id] == NULL)
    28. {
    29. node->next[id] = createTrieNode();
    30. }
    31. node = node->next[id];  // 每插入一步,相当于有一个新串经过,指针向下移动
    32. ++p;
    33. node->count += 1;      // 这行代码用于统计每个单词前缀出现的次数(也包括统计每个单词出现的次数)
    34. }
    35. node->exist = true;        // 单词结束的地方标记此处可以构成一个单词
    36. strcpy(node->trans , trans);
    37. }
    38. char* Trie_search(Trie root, char* word)
    39. {
    40. Trie node = root;
    41. char *p = word;
    42. int id;
    43. while( *p )
    44. {
    45. id = *p - 'a';
    46. node = node->next[id];
    47. ++p;
    48. if(node == NULL)
    49. return 0;
    50. }
    51. if(node->exist)          // 查找成功
    52. return node->trans;
    53. else                     // 查找失败
    54. return NULL;
    55. }
    56. int main(void)
    57. {
    58. Trie root = createTrieNode();     // 初始化字典树的根节点
    59. char str1[3003] , str2[3003] , str[3003] , *p;
    60. int i , k;
    61. scanf("%s",str1);
    62. while(scanf("%s",str1) && strcmp(str1 , "END") != 0)
    63. {
    64. scanf("%s",str2);
    65. Trie_insert(root , str2 , str1);
    66. }
    67. getchar();
    68. gets(str1);
    69. k = 0;
    70. while(gets(str1))
    71. {
    72. if(strcmp(str1 , "END") == 0)
    73. break;
    74. for(i = 0 ; str1[i] != '\0' ; ++i)
    75. {
    76. if(str1[i] >= 'a' && str1[i] <= 'z')
    77. {
    78. str[k++] = str1[i];
    79. }
    80. else
    81. {
    82. str[k] = '\0';
    83. p = Trie_search(root , str);
    84. if(p)
    85. printf("%s", p);
    86. else
    87. printf("%s", str);
    88. k = 0;
    89. printf("%c", str1[i]);
    90. }
    91. }
    92. printf("\n");
    93. }
    94. return 0;
    95. }

Trie树详解及其应用的更多相关文章

  1. trie树--详解

    文章作者:yx_th000 文章来源:Cherish_yimi (http://www.cnblogs.com/cherish_yimi/) 转载请注明,谢谢合作.关键词:trie trie树 数据结 ...

  2. 转:trie树--详解

    前几天学习了并查集和trie树,这里总结一下trie. 本文讨论一棵最简单的trie树,基于英文26个字母组成的字符串,讨论插入字符串.判断前缀是否存在.查找字符串等基本操作:至于trie树的删除单个 ...

  3. [转] Trie树详解及其应用

    一.知识简介         最近在看字符串算法了,其中字典树.AC自动机和后缀树的应用是最广泛的了,下面将会重点介绍下这几个算法的应用.       字典树(Trie)可以保存一些字符串->值 ...

  4. Trie树详解

    1. 概述 Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树.Trie一词来自retrieve,发音为/tri ...

  5. Trie树详解(转)

    特别声明 本文只是一篇笔记类的文章,所以不存在什么抄袭之类的. 以下为我研究时参考过的链接(有很多,这里我只列出我记得的): Trie(字典树)的应用——查找联系人 trie树 Trie树:应用于统计 ...

  6. B树、Trie树详解

    查找(二) 散列表 散列表是普通数组概念的推广.由于对普通数组可以直接寻址,使得能在O(1)时间内访问数组中的任意位置.在散列表中,不是直接把关键字作为数组的下标,而是根据关键字计算出相应的下标. 使 ...

  7. trie字典树详解及应用

    原文链接    http://www.cnblogs.com/freewater/archive/2012/09/11/2680480.html Trie树详解及其应用   一.知识简介        ...

  8. 数据结构图文解析之:AVL树详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  9. Linux DTS(Device Tree Source)设备树详解之二(dts匹配及发挥作用的流程篇)【转】

    转自:https://blog.csdn.net/radianceblau/article/details/74722395 版权声明:本文为博主原创文章,未经博主允许不得转载.如本文对您有帮助,欢迎 ...

随机推荐

  1. Vue.js学习笔记(代码)

    ##v-cloak   v-text   v-html    v-bind    v-on的使用 <!DOCTYPE html> <html> <head> < ...

  2. 在后台启动Redis

    1.下载Redis包,解压到指定位置(这里不再赘述) 2.按“Win +R” 在输入框中输入“cmd” 3.在cmd中打开Redis所在的文件夹,如下图(这是我电脑上的) 4.执行“redis-ser ...

  3. python介绍及下载

    python介绍 Python是一种计算机程序设计语言.是一种面向 对象的动态类型语言,支出被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越来越多的被用于独立的.大型醒 ...

  4. 经验分享:PDF怎么提取页面

    PDF文件的页面有很多但有需要的并不是全部,有时候需要其中一页或几页的时候,这个时候我们就需要把单独的页面提取出来,这个时候应该怎么做呢,上次有小伙伴来询问小编,今天小编就为大家分享一下小编自己的编辑 ...

  5. 自己动手写Redis客户端(C#实现)3 - GET请求和批量回复

    实现代码(C#) 1.发送GET指令 string keyGet = "SetKeyTest"; // 设置 的key StringBuilder sbSendGet = new ...

  6. 利用阿里云搭建frp实现外网远程桌面链接内网电脑

    主要应用场景:针对学生放假回家使用外网无法远程操作学校的服务器或者电脑,这里通过阿里云的云服务器搭建一个frp服务,实现内网穿透,从而可以直接通过远程桌面或者其他工具实现对校园网内的服务器或者电脑进行 ...

  7. ASP.NET Core MVC 授权的扩展:自定义 Authorize Attribute 和 IApplicationModelProvide

    一.概述 ASP.NET Core MVC 提供了基于角色( Role ).声明( Chaim ) 和策略 ( Policy ) 等的授权方式.在实际应用中,可能采用部门( Department , ...

  8. sqlserver 删除表 视图 函数 存储过程

    use tax_ceshiselect 'DROP TABLE '+name from sysobjects where type = 'U'union select 'DROP VIEW '+nam ...

  9. django-celery配置

    1.项目启动顺序: 启动项目: python manage.py runserver 启动celery beat python manage.py celery beat 启动celery worke ...

  10. 为什么hexo预览功能总是间歇性失效?

    个人主题:yilia 有的文章可以正常截断,有的文章不行. 开始我以为这是hexo的一个小bug,但是后来通过查阅GitHub和知乎等网站发现这完全是由于我自己的粗心造成的…手动滑稽 hexo pag ...