【总结】Trie树

Trie,又经常叫前缀树,字典树等等。它有很多变种,如后缀树,Radix Tree/Trie,PATRICIA tree,以及bitwise版本的crit-bit tree。当然很多名字的意义其实有交叉。

定义

在计算机科学中,trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。

trie中的键通常是字符串,但也可以是其它的结构。trie的算法可以很容易地修改为处理其它结构的有序序列,比如一串数字或者形状的排列。比如,bitwise trie中的键是一串位元,可以用于表示整数或者内存地址

基本性质

1,根节点不包含字符,除根节点意外每个节点只包含一个字符。

2,从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。

3,每个节点的所有子节点包含的字符串不相同。

优点:

可以最大限度地减少无谓的字符串比较,故可以用于词频统计和大量字符串排序。

缺点:
虽然不同单词共享前缀,但其实trie是一个以空间换时间的算法。其每一个字符都可能包含至多字符集大小数目的指针(不包含卫星数据)。

应用场景:

(1) 字符串检索
事先将已知的一些字符串(字典)的有关信息保存到trie树里,查找另外一些未知字符串是否出现过或者出现频率。

1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。

(2)文本预测、自动完成,see also,拼写检查

(3)词频统计

==》若无内存限制:Trie + “k-大/小根堆”(k为要找到的数目)。

否则,先hash分段再对每一个段用hash(另一个hash函数)统计词频,再要么利用归并排序的某些特性(如partial_sort),要么利用某使用外存的方法。参考

“海量数据处理之归并、堆排、前K方法的应用:一道面试题” http://www.dataguru.cn/thread-485388-1-1.html。

“算法面试题之统计词频前k大” http://blog.csdn.net/u011077606/article/details/42640867

算法导论笔记——第九章 中位数和顺序统计量

(4)排序

Trie树是一棵多叉树,只要先序遍历整棵树,输出相应的字符串便是按字典序排序的结果。
比如给你N 个互不相同的仅由一个单词构成的英文名,让你将它们按字典序从小到大排序输出。

(5)字符串最长公共前缀
Trie树利用多个字符串的公共前缀来节省存储空间,当我们把大量字符串存储到一棵trie树上时,我们可以快速得到某些字符串的公共前缀。
举例:
给出N 个小写英文字母串,以及Q 个询问,即询问某两个串的最长公共前缀的长度是多少?
解决方案:首先对所有的串建立其对应的字母树。此时发现,对于两个串的最长公共前缀的长度即它们所在结点的公共祖先个数,于是,问题就转化为了离线(Offline)的最近公共祖先(Least Common Ancestor,简称LCA)问题。
而最近公共祖先问题同样是一个经典问题,可以用下面几种方法:

  1. 利用并查集(Disjoint Set),可以采用采用经典的Tarjan 算法;
  2. 求出字母树的欧拉序列(Euler Sequence )后,就可以转为经典的最小值查询(Range Minimum Query,简称RMQ)问题了;

(6)字符串搜索的前缀匹配
trie树常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。
Trie树检索的时间复杂度可以做到n,n是要检索单词的长度,
如果使用暴力检索,需要指数级O(n^2)的时间复杂度。

(7) 作为其他数据结构和算法的辅助结构
如后缀树,AC自动机等

后缀树可以用于全文搜索

转一篇关于几种Trie速度比较的文章:http://www.hankcs.com/nlp/performance-comparison-of-several-trie-tree.html

Trie树和其它数据结构的比较 http://www.raychase.net/1783


【例题】LOJ6469 Magic(trie)

在这里插入图片描述
我们考虑转化问题
假设一个数数位为 x x x(即 1 0 x 10^x 10x),则贡献为 x x x。我们可以看做这个数对 x x x个位置每个的贡献都为 1 1 1,那么对于数位 y y y,我们只需要求有多少对异或完比 1 0 y 10^y 10y大的数,那么就是 y y y这个数位对于答案的贡献了。

可以先建 n n n个数的 01 t r i e 01trie 01trie树,然后令 t t t等于 1 0 y − 1 10^y-1 10y1,将 t t t的各个数位在 t r i e trie trie树上爬就好了。

//size x equals to the number who has its prefix #include<bits/stdc++.h> #define int long long using namespace std; const int N=5e4+5; inline int read() { int X=0; bool flag=1; char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();} while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();} if(flag) return X; return ~(X-1); } int n,a[N],res,cnt; int trie[N<<6][2],siz[N<<6],f[20]; void insert(int s) { int it=0; for(int i=59;i>=0;i--) { int p=((s>>i)&1); if(!trie[it][p]) trie[it][p]=++cnt; it=trie[it][p]; siz[it]++; } } int search(int s,int t) { int it=0,tot=0; for(int i=59;i>=0;i--) { // printf("|%lld %lld|\n",it,sum); int p=((s>>i)&1); if((t>>i)&1) { if(!trie[it][1^p]) break; it=trie[it][1^p]; } else { tot+=siz[trie[it][1^p]]; if(!trie[it][p]) break; it=trie[it][p]; } } return tot; } signed main() { f[0]=1; for(int i=1;i<=18;i++) f[i]=f[i-1]*10; f[19]=f[18]; n=read(); for(int i=1;i<=n;i++) { a[i]=read(); insert(a[i]); } for(int i=18;i>=0;i--) { int tot=0; for(int j=1;j<=n;j++) { tot+=search(a[j],f[i]-1); } res+=tot; } printf("%lld",res/2); }

模板

// string const int N=1e5+5; int n,m,cnt,trie[N][26]; int siz[N]; char s[N]; void insert(char *s) { int it=0,str=strlen(s); for(int i=0;i<str;i++) { if(!trie[it][s[i]-'a']) trie[it][s[i]-'a']=++cnt; it=trie[it][s[i]-'a']; } siz[it]++; } int search(char *s) { int it=0,tot=0,str=strlen(s); for(int i=0;i<str;i++) { if(!trie[it][s[i]-'a']) trie[it][s[i]-'a']=++cnt; it=trie[it][s[i]-'a']; tot+=siz[it]; } return tot; }
//xor const int N=1e5+5; int n,cnt,trie[N<<5][2]; int siz[N<<5],res; void insert(int s) { int it=0; for(int i=31;i>=0;i--) { int p=((s>>i)&1); if(!trie[it][p]) trie[it][p]=++cnt; it=trie[it][p]; } siz[it]++; } int search(int s) { int it=0,tot=0; for(int i=31;i>=0;i--) { int p=((s>>i)&1); if(trie[it][1^p]) tot+=(1<<i),it=trie[it][1^p]; else it=trie[it][p]; } return tot; }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530394.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(18)  评论(0编辑  收藏  举报  
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示