一、知识简介  

      最近在看字符串算法了,其中字典树、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. 经常使用ARM汇编指令

    一面学习,一面总结,一面记录. 以下是整理在网上找到的一些资料,简单整理记录一下,方便以后查阅. ARM处理器的指令集能够分为跳转指令.数据处理指令.程序状态寄存器(PSR)处理指令.载入/存储指令. ...

  2. (step4.3.1) hdu 1010(Tempter of the Bone——DFS)

    题目大意:输入三个整数N,M,T.在接下来的N行.M列会有一系列的字符.其中S表示起点,D表示终点. .表示路 . X表示墙...问狗能有在T秒时到达D.如果能输出YES, 否则输出NO 解题思路:D ...

  3. 用户与 Oracle DB 交互具体过程

    与 Oracle DB 交互 以下的演示样例从最主要的层面描写叙述 Oracle DB 操作.该演示样例说明了一种 Oracle DB 配置,在该配置中,用户和关联server进程执行于通过网络连接的 ...

  4. Qt - 与众不同的电子时钟

     Qt的电子时钟是个老掉牙的demo了,但是利用lcdNumber显示的样子非常老土(下图第一个显示效果),一看就知道是从qt帮助文档里摘出来的example,毫无新意. 美化一下系统时钟,抛开固有控 ...

  5. 设置开机启动时指定非ROOT用户执行相应的脚本

    [root@MSJTVL-MJSP-A01 sm01]# vim /etc/rc.d/rc.local #!/bin/sh # # This script will be executed *afte ...

  6. mysql数据库的高可用方法总结

    高可用架构对于互联网服务基本是标配,无论是应用服务还是数据库服务都需要做到高可用.虽然互联网服务号称7*24小时不间断服务,但多多少少有一 些时候服务不可用,比如某些时候网页打不开,百度不能搜索或者无 ...

  7. mybatis的简单使用

    使用mybatis数据库时,需要添加一下jar包: asm-3.3.1.jarcglib-2.2.2.jarjavassist-3.17.1-GA.jarlog4j-1.2.17.jarmybatis ...

  8. Sass函数--字符串函数

    Sass的函数简介在 Sass 中除了可以定义变量,具有 @extend.%placeholder 和 mixins 等特性之外,还自备了一系列的函数功能.其主要包括: ● 字符串函数 ● 数字函数 ...

  9. C++安装JSONCPP

    VS2013里新建一个空的控制台程序(用作测试jsoncpp是否可用),名为: TestJSON 解压下载好的文件:jsoncpp-src-0.5.0.tar.gz 利用VS2008打开jsoncpp ...

  10. uva 10922 - 2 the 9s

    題目意思:讀取一數字,此數字最大有1000位.計算該數字是否為九的倍數?如是,再計算其階層數. ※判斷是否為九的倍數:所有位數相加 ÷ 9=0,即為九的倍數. ※計算階層數:所有位數相加後得出的第一個 ...