一、知识简介  

      最近在看字符串算法了,其中字典树、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树详解

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

  4. Trie树详解及其应用

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

  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. 数组字符串与指针字符串的区别 char s[]="***" 和char *s="***"的区别

    char s[] = "wangshihui"; char *s = "wangshihui"; 皆宣告了s字符串,在C-style string的函数皆可使用 ...

  2. UDP C/S编程

    UDP C/S编程的步骤如下图所示与TCP C/S通信的区别在于:服务端没有设置监听和等待连接的过程.客户端没有连接服务端的过程.基于UDP的通信时不可靠地,面向无连接的,发送的数据无法确切知道对方收 ...

  3. IntelliJ IDEA 的Project structure说明

    IntelliJ IDEA 的Project structure可以在File->Project structure中打开,同时,在新建项目是IDE一般用向导的方式让你填写Project str ...

  4. 深入浅出 RPC - 浅出篇

    近几年的项目中,服务化和微服务化渐渐成为中大型分布式系统架构的主流方式,而 RPC 在其中扮演着关键的作用.在平时的日常开发中我们都在隐式或显式的使用 RPC,一些刚入行的程序员会感觉 RPC 比较神 ...

  5. 用Ant实现Java项目的自动构建和部署(转)

    Ant是一个Apache基金会下的跨平台的构件工具,它可以实现项目的自动构建和部署等功能.在本文中,主要让读者熟悉怎样将Ant应用到Java项目中,让它简化构建和部署操作. 一.            ...

  6. (转)弹出窗口lhgDialog API文档

    应用到你的项目 如果您使用独立版本的lhgDialog窗口组件,您只需在页面head中引入lhgcore.lhgdialog.min.js文件,4.1.1+版本做了修改可以和jQuerya库同时引用, ...

  7. (转)jQuery Validation Plugin客户端表单证验插件

    jQuery Validation Plugin客户端表单验证插件 官方文档:http://jqueryvalidation.org/documentation/ 官方demo:http://jque ...

  8. 使用Uploadify 时,同时使用了jQuery.Validition 验证控件时,在IE11上出现JS缺少对象错误。

    场景: 使用jQuery.1.8.2 使用 Uploadify 3.2上传控件 使用jQuery.Validition 1.9 验证 使用IE 11 时,当鼠标点击上传按钮时,会出现JS 缺少对象错误 ...

  9. cocos2dx SpriteBatchNode 精灵的渲染优化类

    用处是减少对精灵的渲染次数,用法如下 SpriteFrameCache::getInstance()->addSpriteFramesWithFile("person.plist&qu ...

  10. eclipse使用和优化配置

    一.简介 eclipse 可谓是Java开发界的神器,基本占据了大部分的Java开发市场,而且其官方还对其他语言提供支持,如C++,Ruby,JavaScript等等.为 什么使用它?我想离不开下面的 ...