一、知识简介
字典树(Trie)可以保存一些<字符串,值>的对应关系。类似于c++的map,但是比map快很多。
Trie树的基本性质可以归纳为:
(1)根节点不包含字符,除根节点意外每个节点只包含一个字符。
(2)从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
1、插入过程
对于一个单词,从根开始,沿着单词的各个字母所对应的树中的节点分支向下走,直到单词遍历完,将最后的节点标记为红色,表示该单词已插入Trie树。
2、查询过程
同样的,从根开始按照单词的字母顺序向下遍历trie树,一旦发现某个节点标记不存在或者单词遍历完成而最后的节点未标记为红色,则表示该单词不存在,若最后的节点标记为红色,表示该单词存在。
Trie树的根结点不包含任何信息,第一个字符串为"abc",第一个字母为'a',因此根结点中数组next下标为'a'-97的值不为NULL,其他同理,构建的Trie树如图所示,红色结点表示在该处可以构成一个单词。很显然,如果要查找单词"abc"是否存在,查找长度则为O(len),len为要查找的字符串的长度。而若采用一般的逐个匹配查找,则查找长度为O(len*n),n为字符串的个数。显然基于Trie树的查找效率要高很多。
如上图中:Trie树中存在的就是abc、ab、bd、dda四个单词。在实际的问题中可以将标记颜色的标志位改为数量count等其他符合题目要求的变量。
三、Trie树的操作
在Trie树中主要有3个操作,插入、查找和删除。但一般情况下用不到删除,所以这里不介绍。
1、插入
假设存在字符串str,Trie树的根结点为root,root一开始为0,i一开始为0。
2)i++,继续取str[i],重复1)中的操作,直到遇到结束符'\0',返回当前节点的值。
2、查找
假设要查找的字符串为str,Trie树的根结点为root,root=0,i=root
字典树(Trie)可以保存一些<字符串,值>的对应关系。类似于c++的map,但是比map快很多。
Trie 的优点在于它的时间复杂度。它的插入和查询时间复杂度都为 O(k) ,其中 k 为 key 的长度,与 Trie 中保存了多少个元素无关;
Trie 的缺点是空间消耗很高。
Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。Trie树的基本性质可以归纳为:
(1)根节点不包含字符,除根节点意外每个节点只包含一个字符。
(2)从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
(3)每个节点的所有子节点包含的字符串不相同。
(4)如果字符的种数为n,则每个结点的出度为n,这也是空间换时间的体现,浪费了很多的空间。
(5)插入查找的复杂度为O(n),n为字符串长度。
1、插入过程
对于一个单词,从根开始,沿着单词的各个字母所对应的树中的节点分支向下走,直到单词遍历完,将最后的节点标记为红色,表示该单词已插入Trie树。
2、查询过程
同样的,从根开始按照单词的字母顺序向下遍历trie树,一旦发现某个节点标记不存在或者单词遍历完成而最后的节点未标记为红色,则表示该单词不存在,若最后的节点标记为红色,表示该单词存在。
二、字典树的数据结构:
int trie_cnt = 0;
struct Trie {
int tag, next[next_cnt];
void init() {
tag = 0;
memset(next, -1, sizeof(next));
}
}trie[maxn];
开始的trie_cnt相当于一个内存池,通过增加trie_cnt的数量来模拟新节点的分配
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等其他符合题目要求的变量。
三、Trie树的操作
在Trie树中主要有3个操作,插入、查找和删除。但一般情况下用不到删除,所以这里不介绍。
1、插入
假设存在字符串str,Trie树的根结点为root,root一开始为0,i一开始为0。
1)取str[i],判断trie[root].next[str[i]-97]是否为空。
若为空,则增加trie_node来分配新节点,记新节点的地址为next,把新节点的地址赋给trie[root].next[str[i]-97],然后root = next;
若不为空,则root=trie[root].next[str[i]-97];2)i++,继续取str[i],重复1)中的操作,直到遇到结束符'\0',返回当前节点的值。
2、查找
假设要查找的字符串为str,Trie树的根结点为root,root=0,i=root
1)取str[i],判断判断trie[root].next[str[i]-97]是否为空。
若为空,则返回false;
若不为空,则root=trie[root].next[str[i]-97],继续取字符。
2)重复1)中的操作直到遇到结束符'\0',返回当前节点的值。以下是模板,仅供参考,实际操作还是以题目要求为准:
const int next_cnt = 30;
const int maxn = 1e6 + 10;
int trie_cnt = 0;
struct Trie {
int tag, next[next_cnt];
void init() {
tag = 0;
memset(next, -1, sizeof(next));
}
}trie[maxn];
void init() {
trie_cnt = 0;
trie[0].init();
}
void update(char *str) {
int len = strlen(str), root = 0;
for (int i = 0; i < len; i++) {
int temp = str[i] - 'a';
int next = trie[root].next[temp];
if (next == -1) {
next = ++trie_cnt;
trie[next].init();
trie[root].next[temp] = next;
}
root = next;
}
trie[root].tag = 1;
}
int query(char *str) {
int len = strlen(str), root = 0;
for (int i = 0; i < len; i++) {
int temp = str[i] - 'a';
int next = trie[root].next[temp];
if (next == -1) {
return 0;
}
root = next;
}
return trie[root].tag;
}
最后是一些题目: