【前缀树】
用来保存一个映射(通常情况下 key 为字符串 value 为字符串所代表的信息)
例如:一个单词集合 words = { apple, cat, water } 其中 key 为单词 value 代表该单词是否存在
words[ ‘apple‘ ] = 存在 而 word[ ‘ abc‘ ] = 不存在
图示:一个保存了8个键的trie结构,"A", "to", "tea", "ted", "ten", "i", "in", and "inn".
键不需要被显式地保存在节点中。图示中标注出完整的单词,只是为了演示trie的原理。
【应用】
trie树常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。
补充:
Trie是一种非常简单高效的数据结构,但有大量的应用实例。
(1) 字符串检索
事先将已知的一些字符串(字典)的有关信息保存到trie树里,查找另外一些未知字符串是否出现过或者出现频率。
举例:
@ 给出N 个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。
@ 给出一个词典,其中的单词为不良单词。单词均为小写字母。再给出一段文本,文本的每一行也由小写字母构成。判断文本中是否含有任何不良单词。例如,若rob是不良单词,那么文本problem含有不良单词。
(2)字符串最长公共前缀
Trie树利用多个字符串的公共前缀来节省存储空间,反之,当我们把大量字符串存储到一棵trie树上时,我们可以快速得到某些字符串的公共前缀。
举例:
@ 给出N 个小写英文字母串,以及Q 个询问,即询问某两个串的最长公共前缀的长度是多少?
解决方案:首先对所有的串建立其对应的字母树。此时发现,对于两个串的最长公共前缀的长度即它们所在结点的公共祖先个数,于是,问题就转化为了离线(Offline)的最近公共祖先(Least Common Ancestor,简称LCA)问题。
而最近公共祖先问题同样是一个经典问题,可以用下面几种方法:
1. 利用并查集(Disjoint Set),可以采用采用经典的Tarjan 算法;
2. 求出字母树的欧拉序列(Euler Sequence )后,就可以转为经典的最小值查询(Range Minimum Query,简称RMQ)问题了;
(关于并查集,Tarjan算法,RMQ问题,网上有很多资料。)
(3)排序
Trie树是一棵多叉树,只要先序遍历整棵树,输出相应的字符串便是按字典序排序的结果。
举例:
@ 给你N 个互不相同的仅由一个单词构成的英文名,让你将它们按字典序从小到大排序输出。
(4) 作为其他数据结构和算法的辅助结构
如后缀树,AC自动机等
【模板】
该模板参考白书P209,用数组存储字典树
/* 字典树模板(注意数组初始化问题) by chsobin 给定n个单词的集合,询问q次:一个单词是否在集合中 此处附加信息为节点存不存在 */ #include<iostream> #include<cstdio> #include<cstring> using namespace std; const int maxnode = 100; //maxnode = 单词长度*单词数 int ch[maxnode][27]; int val[maxnode]; //附加信息 int sz = 1; //节点总数 //插入字符串s,附加信息为v。注意v必须非零,0代表本节点不是单词节点 void insert(char * s, int v){ int u=0; int n = strlen(s); int c; for(int i=0;i<n;++i){ c = s[i] - ‘a‘; if(!ch[u][c]){ ch[u][c] = sz++; //新建节点 } u = ch[u][c]; } val[u] = v; //1表示该节点为单词 } bool find(char * s){ int u=0; int n = strlen(s); int c; for(int i=0;i<n;++i){ c = s[i] - ‘a‘; if(ch[u][c]==0) return false; //单词不存在 u = ch[u][c]; } if(val[u]) return true; //判断是否为单词节点 else return false; } int main(){ int n, q; char str[30]; scanf("%d%d", &n, &q); while(n--){ scanf("%s", str); insert(str, 1); //附加信息为1表示该节点为单词节点 } while(q--){ scanf("%s", str); if(find(str)) printf("yes\n"); else printf("No\n"); } return 0; }
【例题】
hdu1251
/* 字典树/前缀树/Trie 2018年1月28日18:42:23 by chsobin 题目大意:给出n个单词(单词长度<=10), q个问题,问以给定字符串开头的单词有多少 朴素思想:遍历 O(qn) 字典树:建树 O(10n) 查询 O(10q) 建树过程中维护节点额外信息val[i]表示建树过程中经过节点i的单词 */ #include<iostream> #include<cstdlib> #include<cstdio> #include<cstring> using namespace std; const int maxn = 1000000; struct Trie{ int ch[maxn][27]; int val[maxn]; int sz; Trie(){ sz = 1; memset(val, 0, sizeof(val)); memset(ch[0], 0, sizeof(ch[0])); } void insert(char * s){ int n = strlen(s); int u = 0; int c; for(int i=0;i<n;++i){ c = s[i] - ‘a‘; if(!ch[u][c]){ memset(ch[sz], 0, sizeof(ch[sz])); ch[u][c] = sz++; } u = ch[u][c]; val[u]++; } } int find(char * s){ int n = strlen(s); int u = 0; int c; for(int i=0;i<n;++i){ c = s[i] - ‘a‘; if(!ch[u][c]){ return 0; } u = ch[u][c]; } return val[u]; } }; Trie trie; char s[15]; int main(){ while(gets(s),strcmp(s,"")){ trie.insert(s); } while(gets(s)!=NULL){ printf("%d\n", trie.find(s)); } return 0; }
【参考】
http://dongxicheng.org/structure/trietree/