参考博文:http://blog.csdn.net/v_july_v/article/details/6897097
第一部分、Trie树
1.1、什么是Trie树

Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。

它有3个基本性质:

根节点不包含字符,除根节点外每一个节点都只包含一个字符。
    从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
    每个节点的所有子节点包含的字符都不相同。

1.2、树的构建
举个在网上流传颇广的例子,如下:
    题目:给你100000个长度不超过10的单词。对于每一个单词,我们要判断他出没出现过,如果出现了,求第一次出现在第几个位置。
    分析:这题当然可以用hash来解决,但是本文重点介绍的是trie树,因为在某些方面它的用途更大。比如说对于某一个单词,我们要询问它的前缀是否出现过。这样hash就不好搞了,而用trie还是很简单。
    现在回到例子中,如果我们用最傻的方法,对于每一个单词,我们都要去查找它前面的单词中是否有它。那么这个算法的复杂度就是O(n^2)。显然对于100000的范围难以接受。现在我们换个思路想。假设我要查询的单词是abcd,那么在他前面的单词中,以b,c,d,f之类开头的我显然不必考虑。而只要找以a开头的中是否存在abcd就可以了。同样的,在以a开头中的单词中,我们只要考虑以b作为第二个字母的,一次次缩小范围和提高针对性,这样一个树的模型就渐渐清晰了。
    好比假设有b,abc,abd,bcd,abcd,efg,hii 这6个单词,我们构建的树就是如下图这样的:

当时第一次看到这幅图的时候,便立马感到此树之不凡构造了。单单从上幅图便可窥知一二,好比大海搜人,立马就能确定东南西北中的到底哪个方位,如此迅速缩小查找的范围和提高查找的针对性,不失为一创举。
    ok,如上图所示,对于每一个节点,从根遍历到他的过程就是一个单词,如果这个节点被标记为红色,就表示这个单词存在,否则不存在。
    那么,对于一个单词,我只要顺着他从根走到对应的节点,再看这个节点是否被标记为红色就可以知道它是否出现过了。把这个节点标记为红色,就相当于插入了这个单词。
    这样一来我们查询和插入可以一起完成(重点体会这个查询和插入是如何一起完成的,稍后,下文具体解释),所用时间仅仅为单词长度,在这一个样例,便是10。
    我们可以看到,trie树每一层的节点数是26^i级别的。所以为了节省空间。我们用动态链表,或者用数组来模拟动态。空间的花费,不会超过单词数×单词长度。
1.3、前缀查询
    上文中提到”比如说对于某一个单词,我们要询问它的前缀是否出现过。这样hash就不好搞了,而用trie还是很简单“。下面,咱们来看看这个前缀查询问题:
    已知n个由小写字母构成的平均长度为10的单词,判断其中是否存在某个串为另一个串的前缀子串。下面对比3种方法:

最容易想到的:即从字符串集中从头往后搜,看每个字符串是否为字符串集中某个字符串的前缀,复杂度为O(n^2)。
    使用hash:我们用hash存下所有字符串的所有的前缀子串,建立存有子串hash的复杂度为O(n*len),而查询的复杂度为O(n)* O(1)= O(n)。
    使用trie:因为当查询如字符串abc是否为某个字符串的前缀时,显然以b,c,d....等不是以a开头的字符串就不用查找了。所以建立trie的复杂度为O(n*len),而建立+查询在trie中是可以同时执行的,建立的过程也就可以成为查询的过程,hash就不能实现这个功能。所以总的复杂度为O(n*len),实际查询的复杂度也只是O(len)。(说白了,就是Trie树的平均高度h为len,所以Trie树的查询复杂度为O(h)=O(len)。好比一棵二叉平衡树的高度为logN,则其查询,插入的平均时间复杂度亦为O(logN))。

下面解释下上述方法3中所说的为什么hash不能将建立与查询同时执行,而Trie树却可以:

在hash中,例如现在要输入两个串911,911456,如果要同时查询这两个串,且查询串的同时若hash中没有则存入。那么,这个查询与建立的过程就是先查询其中一个串911,没有,然后存入9、91、911;而后查询第二个串911456,没有然后存入9、91、911、9114、91145、911456。因为程序没有记忆功能,所以并不知道911在输入数据中出现过,只是照常以例行事,存入9、91、911、9114、911...。也就是说用hash必须先存入所有子串,然后for循环查询。
    而trie树中,存入911后,已经记录911为出现的字符串,在存入911456的过程中就能发现而输出答案;倒过来亦可以,先存入911456,在存入911时,当指针指向最后一个1时,程序会发现这个1已经存在,说明911必定是某个字符串的前缀。

读者反馈@悠悠长风:关于这点,我有不同的看法。hash也是可以实现边建立边查询的啊。当插入911时,需要一个额外的标志位,表示它是一个完整的单词。在处理911456时,也是按照前面的查询9,91,911,当查询911时,是可以找到前面插入的911,且通过标志位知道911为一个完整单词。那么就可以判断出911为911456的前缀啊。虽然trie树更适合这个问题,但是我认为hash也是可以实现边建立,边查找。
   吾答曰:但若反过来呢?比如说是先查询911456,而后查询911呢?你的在hash中做一个完整单词的标志就行不通了。因为,你查询911456时,并不知道后来911会是一个完整的单词。
    至于,有关Trie树的查找,插入等操作的实现代码,网上遍地开花且千篇一律,诸君尽可参考,想必不用我再做多余费神。
1.4、查询
    Trie树是简单但实用的数据结构,通常用于实现字典查询。我们做即时响应用户输入的AJAX搜索框时,就是Trie开始。本质上,Trie是一颗存储多个字符串的树。相邻节点间的边代表一个字符,这样树的每条分支代表一则子串,而树的叶节点则代表完整的字符串。和普通树不同的地方是,相同的字符串前缀共享同一条分支。下面,再举一个例子。给出一组单词,inn, int, at, age, adv, ant, 我们可以得到下面的Trie:

可以看出:

每条边对应一个字母。
    每个节点对应一项前缀。叶节点对应最长前缀,即单词本身。
    单词inn与单词int有共同的前缀“in”, 因此他们共享左边的一条分支,root->i->in。同理,ate, age, adv, 和ant共享前缀"a",所以他们共享从根节点到节点"a"的边。

查询操纵非常简单。比如要查找int,顺着路径i -> in -> int就找到了。

搭建Trie的基本算法也很简单,无非是逐一把每则单词的每个字母插入Trie。插入前先看前缀是否存在。如果存在,就共享,否则创建对应的节点和边。比如要插入单词add,就有下面几步:

考察前缀"a",发现边a已经存在。于是顺着边a走到节点a。
    考察剩下的字符串"dd"的前缀"d",发现从节点a出发,已经有边d存在。于是顺着边d走到节点ad
    考察最后一个字符"d",这下从节点ad出发没有边d了,于是创建节点ad的子节点add,并把边ad->add标记为d。

1.5、Trie树的应用
    除了本文引言处所述的问题能应用Trie树解决之外,Trie树还能解决下述问题(节选自此文:海量数据处理面试题集锦):

3、有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。
    9、1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。请怎么设计和实现?
    10、 一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。
    13、寻找热门查询:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录,这些查询串的重复读比较高,虽然总数是1千万,但是如果去除重复和,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就越热门。请你统计最热门的10个查询串,要求使用的内存不能超过1G。
    (1) 请描述你解决这个问题的思路;
    (2) 请给出主要的处理流程,算法,以及算法的复杂度。

1.6、Tire树的实现

/*Trie树(字典树) 2011.10.10*/ #include <iostream>
#include<cstdlib>
#define MAX 26
using namespace std; typedef struct TrieNode                     //Trie结点声明
{ bool isStr;                            //标记该结点处是否构成单词  struct TrieNode *next[MAX];            //儿子分支
}Trie; void insert(Trie *root,const char *s)     //将单词s插入到字典树中
{ if(root==NULL||*s=='\0') return; int i; Trie *p=root; while(*s!='\0') { if(p->next[*s-'a']==NULL)        //如果不存在,则建立结点  { Trie *temp=(Trie *)malloc(sizeof(Trie)); for(i=0;i<MAX;i++) { temp->next[i]=NULL; } temp->isStr=false; p->next[*s-'a']=temp; p=p->next[*s-'a'];    }    else{ p=p->next[*s-'a']; } s++; } p->isStr=true;                       //单词结束的地方标记此处可以构成一个单词
} int search(Trie *root,const char *s)  //查找某个单词是否已经存在
{ Trie *p=root; while(p!=NULL&&*s!='\0') { p=p->next[*s-'a']; s++; } return (p!=NULL&&p->isStr==true);      //在单词结束处的标记为true时,单词才存在
} void del(Trie *root)                      //释放整个字典树占的堆区空间
{ int i; for(i=0;i<MAX;i++) { if(root->next[i]!=NULL) { del(root->next[i]); } } free(root);
} int main(int argc, char *argv[])
{ int i; int n,m;                              //n为建立Trie树输入的单词数,m为要查找的单词数  char s[100]; Trie *root= (Trie *)malloc(sizeof(Trie)); for(i=0;i<MAX;i++) { root->next[i]=NULL; } root->isStr=false; scanf("%d",&n); getchar(); for(i=0;i<n;i++)                 //先建立字典树  { scanf("%s",s); insert(root,s); } while(scanf("%d",&m)!=EOF) { for(i=0;i<m;i++)                 //查找  { scanf("%s",s); if(search(root,s)==1) printf("YES\n"); elseprintf("NO\n"); } printf("\n");    } del(root);                         //释放空间很重要  return 0;
}

————————————————
版权声明:本文为CSDN博主「ts173383201」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ts173383201/article/details/7858598

Tire树(字典树)相关推荐

  1. HDU - 5790 Prefix(主席树+字典树)

    题目链接:点击查看 题目大意:给出 n 个字符串,再给出 m 次询问,每次询问需要输出区间 [ l , r ] 内的所有字符串有多少个不同的前缀,要求算法强制在线 题目分析:统计字符串的前缀,不难想到 ...

  2. Algorithm:树结构(二叉树/多路查找树/字典树)的简介、具体结构(FBT/CBT/BST/BBT/Heap/Huffman、B树/B+树/R树、字典树)及其运算(增删查/遍历/旋转)、代码实现

    Algorithm:树结构(二叉树/多路查找树/字典树)的简介.具体结构(FBT/CBT/BST/BBT/Heap/Huffman.B树/B+树/R树.字典树)及其运算(增删查/遍历/旋转).代码实现 ...

  3. Trie(前缀树/字典树)及其应用

    from:https://www.cnblogs.com/justinh/p/7716421.html Trie,又经常叫前缀树,字典树等等.它有很多变种,如后缀树,Radix Tree/Trie,P ...

  4. Luogu P2580 于是他错误的点名开始了 Trie树 字典树

    字典树裸题.每次插入询问串,查询的时候拿出来直接查,信息保留在节点上. #include <bits/stdc++.h> using namespace std;char s[51]; i ...

  5. 【数据结构】前缀树/字典树

    目录 1.概述 2.代码实现 3.应用 本文参考: LeetCode 208.实现 Trie (前缀树) 1.概述 前缀树又称字典树.Trie 树.单词查找树,是一棵有根树,同时也是一种哈希树的变种, ...

  6. 基于Tire树(字典树)与倒排索引实现文本词频统计工具

    文章目录 文件读写操作 C风格文件读取 C++风格按行读取 C++风格按单词读取 实现文件词频统计工具 英文文章单词的正确分割 基于Trie树实现文件词频统计 基于Trie树实现带倒排索引的文件词频统 ...

  7. Tired树(字典树)理解与例题

    例题:Trie字符串统计 模板 int son[N][26], cnt[N], idx; //idx 当前用到了的结点的下标 // 0号点既是根节点,又是空节点 // son[][]存储树中每个节点的 ...

  8. 字典树 [字典树相关扩展与应用字典树AC自动机] _ CodingPark编程公园

    基础知识 print()语句中的end=" "的含义 Print不换行:end传递一个空字符串,这样print函数不会在字符串末尾添加一个换行符,而是添加一个空字符串 doctes ...

  9. 字典树——字典树树模板

    就是26//52叉树//模板 #define MAX 26 struct Trie {Trie *next[maxn];int v;Trie(){v=0;for(int i=0;i<maxn;i ...

  10. 字典树(前缀树)-Java实现

    字典树 字典树是一种树形结构,优点是利用字符串的公共前缀来节约存储空间.在这提供一个自己写的Java实现,非常简洁. 根节点没有字符路径.除根节点外,每一个节点都被一个字符路径找到. 从根节点到某一节 ...

最新文章

  1. 离开互联网上岸1年后,我后悔了!重回大厂内卷
  2. Nexenta和ParaScale发布开源存储产品
  3. Dumpzilla工具第615行bug的解决办法
  4. java数组显示最大值,java 如何用方法在数组中找到最大值并显示他的名称?
  5. python数组内运算_有效的数学运算在Python中用cython进行小数组运算
  6. swfupload添加上传进度条(转)
  7. WinAPI-CreateMutex(双开)
  8. js 调用百度地图,并且定位用户地址,显示省市区街,经纬度
  9. 最小生成树、二分图问题概述
  10. TabLayout实现自定义标题栏目功能
  11. LINUX修改.bashrc之后,生效的办法
  12. 为什么人人都应该学编程?
  13. 软件收集-建筑工程资料软件
  14. windows server 2012 安装 VC++ 安装失败0x80240017解决方法
  15. 2020中北大学计算机调剂名额,2020年中北大学考研调剂信息
  16. Unity之引导功能遮罩事件穿透
  17. KVM 核心技术详解
  18. [libxml2]_[C/C++]_[使用libxml2读取分析xml文件]
  19. python从入门到实践19章答案
  20. 疫情下的企业应对之道:企业如何降本提质增效

热门文章

  1. 四个方面分析SEO如何提高网站的权重
  2. 关于n维和n-1维欧式空间的理解(转)
  3. landesk 卸载_LANDesk软件分发在项目中的深入探索(续2)—客户端已安装应用程序的远程卸载...
  4. python让solidworks自动建模_让机器学习自动帮我们建模,这4个Python库能让你大开眼界...
  5. 缺少所需的CD/DVD驱动器设备驱动程序
  6. 数组根据条件筛选出满足条件的数据(数组里面是对象)
  7. Android通知的使用及设置
  8. 强化理解指针、指针数组和数组指针(从三味书屋到成华大道)
  9. 3D建模入门,Zbrush插件zwrap拓扑技巧教程,仅适用24K纯萌新!
  10. 扩展名是.ps的PostScript文件详解