一、知识简介  
      最近在看字符串算法了,其中字典树、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. 模拟实现库函数的atoi、atof和itoa

    1.函数atoi atoi (表示 alphanumeric to integer)是把字符串转换成整型数的一个函数.广泛的应用在计算机程序和办公软件中.atoi( ) 函数会扫描参数 nptr字符串 ...

  2. Initialize the shader 初始化着色器

    目录 Loads the shader files and makes it usable to DirectX and the GPU 加载着色器文件并使其可用于DirectX和GPU Compil ...

  3. unity Tab键实现切换输入框功能

    using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; ...

  4. js实现页面重新加载

    https://blog.csdn.net/wangjian530/article/details/80596801

  5. RabbitMQ原理图

    一.RabbitMQ 原理图 二.Rabbit 交换器讲解 1 Direct 交换器(发布与订阅 完全匹配)                       1.2搭建环境               1 ...

  6. ELK:logstash和filebeat6.0及以上版本的配置

    filebeat6.0版本以上没有document_type字段,因此需要另外标记下或者代替document_type字段的功能 案例如下: fielbeat5.5的配置 logstash5.5的配置 ...

  7. 防御xss 大致理解

    前端 发送留言内容 包含 js 代码 后台 管理员 查看 留言 代码被执行 拿到cookie 成功登陆 解决办法 对录入 进行相关的过滤处理 其他解决方法 正在学习中

  8. 四、OpenStack—glance组件介绍与安装

    一.glance介绍 Glance是Openstack项目中负责镜像管理的模块,其功能包括虚拟机镜像的查找.注册和检索等. Glance提供Restful API可以查询虚拟机镜像的metadata及 ...

  9. WPF RadioButton的绑定

    1. 枚举类 public enum EnumDataTypes { Simulation, Test } 2. 枚举型与布尔型的转换 public class EnumToBooleanConver ...

  10. Git 通过ssh 配置基于Host的差异配置

    Host gitlab.xxx.com HostName gitlab.xxx.com User user IdentityFile xxx\.ssh\id_rsa Host github.com H ...