字典树(Trie)
试想一个经典的场景:
有一个字符串集合 C = [s1, s2, …, sn],给定字符串 t,要判断 t是否在集合 C中
解法一
利用遍历的思想,逐个遍历字符串数组:
#C = [s1, s2, ..., sn]
#t = 'xxx.xxx'
def find(C, t):
for s in C:
if t == s:
return True
return False
假设字符串的平均长度为m,t的长度为k,C中有n个元素,这样做的时间复杂度为 O ( m i n ( m , k ) ∗ n ) = O ( n m ) O(min(m, k)*n) = O(nm) O(min(m,k)∗n)=O(nm).
如果我们想达到更低的时间复杂度,就需要利用到字典树.
解法二:字典树
首先需要回答的是:什么是字典树?
字典树: 利用字符串的前缀来构建的一棵保存若干个字符串信息的树
![image-20201106101802082](https://i-blog.csdnimg.cn/blog_migrate/a58d42417124d6c717e23622bbe0ff72.png#pic_center)
从根节点到叶节点的路径上的边构成了一个完成的字符串,例如路径:0->1->2->3,表示字符串:“inn”.
字典树的构建
字典树的构建关键在于构建一条路径,使得从根节点到叶节点的路径代表着字符串本身。由于是从根节点开始往下,考虑相同前缀的情况:例如"int"和"inn"就有着相同的前缀,那么无需重新添加新的结点,直接共用结点0,1,2即可.
由此我们得出了构建字典树的算法:
- 建立字典树的根结点 0,每个字符串都将从 0开始往下搜索
- 从集合 C中取一个字符串 s,设置树的遍历指针 p为根结点 0
- 在字符串 s中取一个字符 c,如果指针 p指向的结点存在一条边的值为 c,那么把p指向这条边所指向的结点,重复过程3;否则,生成新的值为 c的边 e,并添加一个新的结点 v,让 e指向 v,p指向新的结点v. 直到取完完字符串s
- 重复过程2直到集合 C中元素被取完
为了更好的理解上述算法过程,我们举一个栗子(例子!).
例1:字符串集合 C = [‘inn’, ‘int’],要构建一颗字典树.
- 创建根结点0
![image-20201106101802082](https://i-blog.csdnimg.cn/blog_migrate/77be6f3b47b5c6ac246a16e7c0411135.png#pic_center)
- 建立’inn’的路径
- 建立’int’的路径
![](https://i-blog.csdnimg.cn/blog_migrate/967f05833e55427b8d69ef515a22f18f.png#pic_center)
![image-20201106104953137](https://i-blog.csdnimg.cn/blog_migrate/72814f74fb538d69c4bd520a2ee1cadd.png#pic_center)
Talk is cheap. Show me your code
下面给出构建字典树的python版本.
from typing import List
alphabet_size = 200
max_str_size = 1000
def build_dict_tree(words: List[str]) -> List[List[int]]:
dict_tree = [[-1] * alphabet_size for _ in range(max_str_size)]
node_num = 1
for word in words:
p = 0
for c in word:
c = ord(c)
assert(0 <= c < alphabet_size)
if dict_tree[p