Trie
a kind of search tree—an ordered tree data structure used to store a dynamic set or associative array where the keys are usually strings
–维基百科
在计算机科学中,Trie,又称字典树、单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
–百度百科
Trie的3特征
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符都不相同。
Trie的目的
以信息换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的
Trie的不同种类
- 经典trie
- 动态数组trie
- hashtrie
- double array trie
- 压缩trie
Trie的实现(python)
Hash Trie 树
class Node():
'''
Node类: Trie中的节点
实例node的属性 children:
这里采用python的字典来映射,{str:Node} Node:当前节点的子节点;str:子节点Node代表的字符。
实例node的属性 isword:
True:代表从根节点到当前节点经过的字符组成的字符串存在于Trie中。
False:代表从根节点到当前节点经过的字符组成的字符串不存在于Trie中。
'''
def __init__(self):
self.children = {}
self.isword = False
class Trie():
def __init__(self):
self.root = Node()
def insert(self,strs):
'''插入原理:
1将字符串分为单个字符,
2取根节点和第一个字符
3判断当前节点的children属性中是否存在该字符,
3.1若存在,则进入该字符对应的节点,重复步骤3判断下一个字符
3.2若有字符不存在,在children属性中为该字符映射新节点,进入新节点,重复步骤3判断下个字符
4直至所有字符判断结束,最后一个节点isword属性设为true,表示该strs已插入成功
'''
node = self.root
for s in strs:
if s not in node.children:
node.children[s] = Node()
node = node.children[s]
node.isword = True
def find(self,strs):
''' 查找原理:
1将字符串分为单个字符,
2取根节点和第一个字符
3判断当前节点的children属性中是否存在该字符,
3.1若存在,则进入该字符对应的节点,重复步骤3判断下一个字符,直至所有字符判断结束
3.1.1 末节点的属性isword=True,则strs在Trie中,结果为找到
3.1.2 isword=False,则不在,结果为未找到
3.2若有字符不存在则strs不在trie中,结果为未找到
4返回查找结果
'''
node = self.root
for s in strs:
if s in node.children:
node = node.children[s]
else:
return False
return node.isword
立即创建属于自己的trie树
Trie应用
统计
本题来自
http://acm.hdu.edu.cn/showproblem.php?pid=1251
Input:
输入数据的第一部分是一张单词表,每行一个单词,单词的长度不超过10,它们代表的是老师交给Ignatius统计的单词,一个空行代表单词表的结束.第二部分是一连串的提问,每行一个提问,每个提问都是一个字符串.注意:本题只有一组测试数据,处理到文件结束.
Output:
对于每个提问,给出以该字符串为前缀的单词的数量.
例如
Sample Input: | Sample query: | Sample Output: |
---|---|---|
banana | ba | 2 |
band | b | 3 |
bee | band | 1 |
absolute | abc | 0 |
acm |
思路:
1.改造一下find方法,让它返回最后一个字符串代表的node
def findprefix(self,prefix):
....
return node
2.构造findprefixword方法,接收参数prefix,node,words=[],
返回len(words)
def finprefixwords(self,prefix,node,words=[]):
'''
str:前缀
node:该前缀代表的节点
words:所有trie中存储的已该前缀开头的词
'''
l = len(node.children)
if l == 0:
words.append(prefix)
else:
if node.isword:
words.append(prefix)
for key in node.children:
self.finprefixwords(prefix+key,node.children[key],words)
return len(words)
LeetCode211. 添加与搜索单词 - 数据结构设计
LeetCode745. 前缀和后缀搜索
LeetCode1032. 字符流
待解决问题:
寻找热门查询:
搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录,这些查询串的重复读比较高,虽然总数是1千万,但是如果去除重复和,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就越热门。请你统计最热门的10个查询串,要求使用的内存不能超过1G(京东笔试题简答题与此类似)。
(1) 请描述你解决这个问题的思路
用python的dict(即hashtable)记录查询串,key为查询串,value=出现次数
1g=1024^3b,设每个查询串都为255b,1g可以存大约400w个。
(2) 请给出主要的处理流程,算法,以及算法的复杂度。
字符串最长公共前缀
Trie树利用多个字符串的公共前缀来节省存储空间,反之,当我们把大量字符串存储到一棵trie树上时,我们可以快速得到某些字符串的公共前缀。
举例:
- 给出N 个小写英文字母串,以及Q 个询问,即询问某两个串的最长公共前缀的长度是多少.
解决方案:
首先对所有的串建立其对应的字母树。此时发现,对于两个串的最长公共前缀的长度即它们所在结点的公共祖先个数,于是,问题就转化为了离线 (Offline)的最近公共祖先(Least Common Ancestor,简称LCA)问题。 而最近公共祖先问题同样是一个经典问题,可以用下面几种方法:
- 利用并查集(Disjoint Set),可以采用采用经典的Tarjan 算法;
- 求出字母树的欧拉序列(Euler Sequence )后,就可以转为经典的最小值查询(Range Minimum Query,简称RMQ)问题了;
排序
Trie树是一棵多叉树,只要先序遍历整棵树,输出相应的字符串便是按字典序排序的结果。
举个栗子: 给你N 个互不相同的仅由一个单词构成的英文名,让你将它们按字典序从小到大排序输出。
作为其他数据结构和算法的辅助结构
如后缀树,AC自动机等。