海量数据处理之Tire树(字典树)
原文:http://blog.csdn.net/ts173383201/article/details/7858598
参考博文:http://blog.csdn.net/v_july_v/article/details/6897097
第一部分、Trie树
1.1、什么是Trie树
Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
它有3个基本性质:
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符都不相同。
1.2、树的构建
分析:这题当然可以用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、前缀查询
- 最容易想到的:即从字符串集中从头往后搜,看每个字符串是否为字符串集中某个字符串的前缀,复杂度为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))。
- 在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必定是某个字符串的前缀。
1.4、查询
可以看出:
- 每条边对应一个字母。
- 每个节点对应一项前缀。叶节点对应最长前缀,即单词本身。
- 单词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树的应用
- 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");
- else
- printf("NO\n");
- }
- printf("\n");
- }
- del(root); //释放空间很重要
- return 0;
- }
海量数据处理之Tire树(字典树)相关推荐
- HDU - 5790 Prefix(主席树+字典树)
题目链接:点击查看 题目大意:给出 n 个字符串,再给出 m 次询问,每次询问需要输出区间 [ l , r ] 内的所有字符串有多少个不同的前缀,要求算法强制在线 题目分析:统计字符串的前缀,不难想到 ...
- Algorithm:树结构(二叉树/多路查找树/字典树)的简介、具体结构(FBT/CBT/BST/BBT/Heap/Huffman、B树/B+树/R树、字典树)及其运算(增删查/遍历/旋转)、代码实现
Algorithm:树结构(二叉树/多路查找树/字典树)的简介.具体结构(FBT/CBT/BST/BBT/Heap/Huffman.B树/B+树/R树.字典树)及其运算(增删查/遍历/旋转).代码实现 ...
- Trie(前缀树/字典树)及其应用
from:https://www.cnblogs.com/justinh/p/7716421.html Trie,又经常叫前缀树,字典树等等.它有很多变种,如后缀树,Radix Tree/Trie,P ...
- Luogu P2580 于是他错误的点名开始了 Trie树 字典树
字典树裸题.每次插入询问串,查询的时候拿出来直接查,信息保留在节点上. #include <bits/stdc++.h> using namespace std;char s[51]; i ...
- 【数据结构】前缀树/字典树
目录 1.概述 2.代码实现 3.应用 本文参考: LeetCode 208.实现 Trie (前缀树) 1.概述 前缀树又称字典树.Trie 树.单词查找树,是一棵有根树,同时也是一种哈希树的变种, ...
- 基于Tire树(字典树)与倒排索引实现文本词频统计工具
文章目录 文件读写操作 C风格文件读取 C++风格按行读取 C++风格按单词读取 实现文件词频统计工具 英文文章单词的正确分割 基于Trie树实现文件词频统计 基于Trie树实现带倒排索引的文件词频统 ...
- Tired树(字典树)理解与例题
例题:Trie字符串统计 模板 int son[N][26], cnt[N], idx; //idx 当前用到了的结点的下标 // 0号点既是根节点,又是空节点 // son[][]存储树中每个节点的 ...
- 字典树 [字典树相关扩展与应用字典树AC自动机] _ CodingPark编程公园
基础知识 print()语句中的end=" "的含义 Print不换行:end传递一个空字符串,这样print函数不会在字符串末尾添加一个换行符,而是添加一个空字符串 doctes ...
- 字典树——字典树树模板
就是26//52叉树//模板 #define MAX 26 struct Trie {Trie *next[maxn];int v;Trie(){v=0;for(int i=0;i<maxn;i ...
- 海量数据处理-Python
文章目录 海量数据处理-Python 海量数据处理的困难 大文件生成 空间受限 分块读取 文件拆分提取 拆分小文件 比较小文件 通过hash拆分文件 拆分小文件-依据hash 求取IP前TopK(还是 ...
最新文章
- poj2017——Speed Limit
- 通过传入的栏目编号找出并返回当前栏目编号及其所有子栏目编号
- 移动端数据统计,精细化运营的永动机
- 自动从mysql下载文件到powerbi_关于在Power Query和Power BI中从网页下载文件的小提示...
- [图解]小白都能看懂的FASTER R-CNN – 原理和实现细节
- 程序人生:什么是“对用户友好”
- 微软企业库5.0学习笔记(三十三)数据访问模块
- 如何将maven项目打包成可执行的jar
- json同一个参数不同类型_js基础之变量类型
- java 中文路径 读取_Java读取文件时中文路径处理
- Javascript:遍历json数据的方法
- Google卫片下载(转)
- 我对Pass、SaaS、IaaS的理解
- 最新Axure激活码
- idea导出数据库的可执行sql文件
- linux某用户 计划任务,Linux计划任务管理
- 电商后台管理系统-权限管理模块
- 赛码行测题库_行测数字推理题库
- zynq pl 发数据给 ps
- 我上网下载了rar压缩文件,有密码,用arpr工具怎么破解不了,_压缩文件密码
热门文章
- 用python定义一个员工类_Python与类一起工作
- rk3288 android 6.0固件,Firefly-RK3288主板烧写官方Android固件起不来
- 技术类应届生面试技巧(牛客网)
- ghost linux 黑屏,Ghost 与 Linux 的兼容性
- 【收集】Oracle官网账号
- 域名系统的主要功能是什么?域名系统中的根服务器和权威服务器有何区别?权威服务器与管辖区有何关系?
- vant/vue获取商品筛选分类接口以及替换原有的键名
- java list筛选数据_java 根据条件在List中筛选出符合条件的对象
- PHP格式化 插件 vs code
- android 保存图片,并通知相册更新