什么是Trie树?
Trie树, 又叫字典树、前缀树、单词查找树, 呈多叉树结构, 是一种能够高效存储和查找字符串的数据结构。
上述图是一棵Trie树, 有abate、about、abeve、have、you、love、to这些单词。
上述图可以得出Trie树的简单性质:
1,根节点不包含字符,除根节点意外每个节点只包含一个字符。 2,从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。 3,每个节点的所有子节点包含的字符串不相同。
在使用这个数据结构时, 通常会给每个输入的字符的最后一个节点加上标记, 用来说明此处是否构成了一个单词(或者关键字)。
Trie树的优缺点
优点:
-
查询和插入的效率都很高,都是O(m),m为待查询和待插入的字符串长度。
-
Trie树的查询不会出现冲突。
-
Trir树可以对关键字按字典序排序。
缺点:
-
当hash函数很好时, Trie的查找效率会低于哈希搜索。
-
空间占用量大。
Trie树的构建
下面使用数组来构建Trie树
全局变量:
N = 100010
son = [[0 for i in range(26)] for j in range(100010)]
cnt = [0] * N
idex = 0
全局变量的解释:
这个idx是新需要插入的节点的编号,我们知道trie高效的原因之一是储存了公共的前缀, 所以这些前缀节点再次插入的时候,是不需要另外储存的,idx只是需要新储存的节点编号。
son这个矩阵有多个用处。
如果son[i]这一行由元素不为0,那么它表示的是某个字母,至于是哪个字母下一段代码再分解。 它储存的值,是下一个新插入元素的位置。
注意如果要插入停止符,也会单独占用一个idx。
cnt是对应每个停止符的数量。
insert函数:
def insert(x):
global son, cnt, idex
p = 0 # 从根结点开始遍历
for i in x:
u = ord(i) - ord('a')
if not son[p][u]: # 如果没有该字母的子结点, 就创建一个
idex += 1
son[p][u] = idex
p = son[p][u] # 走到p的子结点
cnt[p] += 1
query函数:
def query(x):
global son, cnt
p = 0 # 从根结点开始遍历
for i in x:
u = ord(i) - ord('a')
if not son[p][u]: # 如果没有子结点, 说明没有该字符串, 就返回0
return 0
p = son[p][u] # 在Trie中不断走下去
# 如果该字符串结尾有标记, 则输出
return cnt[p]
完整代码:
N = 100010
son = [[0 for i in range(26)] for j in range(100010)]
cnt = [0] * N
idex = 0
def insert(x):
global son, cnt, idex
p = 0 # 从根结点开始遍历
for i in x:
u = ord(i) - ord('a')
if not son[p][u]: # 如果没有该字母的子结点, 就创建一个
idex += 1
son[p][u] = idex
p = son[p][u] # 走到p的子结点
cnt[p] += 1
def query(x):
global son, cnt
p = 0 # 从根结点开始遍历
for i in x:
u = ord(i) - ord('a')
if not son[p][u]: # 如果没有子结点, 说明没有该字符串, 就返回0
return 0
p = son[p][u] # 在Trie中不断走下去
# 如果该字符串结尾有标记, 则输出
return cnt[p]
if __name__ == "__main__":
n = int(input()) # 要进行插入和查询操作的总数
arr = []
while n:
n -= 1
q = list(map(str, input().split()))
if q[0] == 'I':
insert(q[1])
else:
print(query(q[1]))
基于Trie树写的一个小词典
import re
class TrieNode(object):
def __init__(self): # 使用数组来模拟Trie树来构建单词表
with open(r"输入记忆功能文档的地址", 'r', encoding='utf-8') as filename:
lines = filename.readlines()
self.son = []
for line in lines: # 恢复son的记忆
row = list(map(int, line.strip().split(',')))
self.son.append(row)
self.cnt = [] # 单词结束的标记
with open(r"输入记忆标记文档的地址", 'r', encoding='utf-8') as filename:
lines = filename.readline()
self.cnt = list(map(int, lines.strip().split(',')))
self.Chinese = {} # 用字典来存储英文和中文
with open(r"输入记忆字典文档的地址", 'r', encoding='utf-8') as filename:
EC_str = filename.read()
self.Chinese = eval(EC_str)
with open(r"输入记忆步数文档的地址", 'r', encoding='utf-8') as filename:
self.idex = int(filename.read())
with open(r"输入记忆总数文档的地址", 'r', encoding='utf-8') as filename:
self.num = int(filename.read())
def add(self, English_word, soundmark, *args): # *args 参数实现多个不确定中文释义的添加
if English_word == '' or soundmark == '' or args == ():
print('请重新检查输入的格式是否正确!')
return
English_word = English_word.lower() # 防止读者输入大写单词导致爆列表
p = 0 # 根结点开始遍历单词
for i in English_word:
u = ord(i) - ord('a')
if not self.son[p][u]:
self.idex += 1
self.son[p][u] = self.idex
p = self.son[p][u]
if self.cnt[p] == 0: # 如果该单词无标记, 则添加把中英词添加进词典
# 向字典中加入英文和中文释义
chinese = ' '.join(tuple([soundmark]) + tuple(args[0])) # 单个字符串以原格式存储在元组中, 需转成列表之后在转换成元组类型。
self.Chinese.setdefault(English_word, chinese) # 向字典添加键值对, 即对应单词的英标和中文释义
self.cnt[p] += 1 # 设置该单词的标记
self.num += 1 # 统计词典中的单词总数
with open(r"C:\Apps\pycharm file storage\项目1 高效查询中英词典\记忆功能", 'w', encoding='utf-8') as filename:
for row in self.son:
line = ",".join(str(x) for x in row) + "\n"
filename.write(line) # 每次读入都覆写原有son
with open(r"C:\Apps\pycharm file storage\项目1 高效查询中英词典\记忆标记", 'w', encoding='utf-8') as filename:
cnt_str = ','.join(list(map(str, self.cnt))) # 覆写原有标记
filename.write(cnt_str)
with open(r"C:\Apps\pycharm file storage\项目1 高效查询中英词典\记忆字典", 'w', encoding='utf-8') as filename:
dict_str = str(self.Chinese)
filename.write(dict_str)
with open(r"C:\Apps\pycharm file storage\项目1 高效查询中英词典\记忆步数", 'w', encoding='utf-8') as filename:
filename.write(str(self.idex))
with open(r"C:\Apps\pycharm file storage\项目1 高效查询中英词典\记忆总数", 'w', encoding='utf-8') as filename:
filename.write(str(self.num))
else:
print('该词典中已经存在该单词!')
def query_English(self, English_word): # 通过英文来查找中文
if English_word == '': # 判断输入是否为空
print('请检查输入是否为空!')
return
English_word = English_word.lower() # 防止读者输入大写单词导致爆列表
p = 0 # 从根结点开始搜寻单词
for i in English_word:
u = ord(i) - ord('a')
if not self.son[p][u]:
print('词典中无该单词!')
return
p = self.son[p][u]
if self.cnt[p] != 0:
print(self.Chinese[English_word])
def query_Chinese(self, *args): # 通过中文来查询英文
if args == '':
print('请检查输入是否为空!')
return
if self.Chinese:
flag = False # 判断是否退出中文字典的循环
flag1 = 1 # 判断是否是否有这些中文释义
for key, value in self.Chinese.items():
'''if isinstance(value, tuple) and args in value:
print(key, value[0])
break
else:
print('无该中文释义对应的单词在词典中!')'''
for para in args: # 通过单个或者多个中文来查找词典中是否有该单词
if para in value:
print(key)
flag = True
flag1 -= 1
break
if flag: # 找到了就退出该功能
break
if flag1:
print('该词典中无此中文释义所对应的单词!')
else:
print('词典中没有单词!请先输入单词!')
def delete(self, English_word):
# 判断输入是否为空
if English_word == '':
print('请检查输入是否为空!')
return
English_word = English_word.lower() # 防止读者输入大写单词导致爆列表
# 先判断是否词典中是否有这个单词
if English_word in self.Chinese:
p = 0 # 根结点开始遍历单词
for i in English_word:
u = ord(i) - ord('a')
p = self.son[p][u] # 通过Trie树找到该单词标记所在的索引位置
self.cnt[p] = 0 # 把标记重新设置为零就是删除了该单词在Trie树中的存在
self.Chinese.pop(English_word) # 删除了单词在字典中的存在
print(' 该单词已删除!')
else:
print('该词典中没有这个单词! 无需删除!')
def show(self):
for key, value in self.Chinese.items():
print('{}: \t {}'.format(key, value))
def main(self):
print(' 欢迎使用中英单词笔记本!!!!!!!!')
print(' 1.添加单词(输入格式:英文-音标-中文)')
print(' 2.查找英文单词(输入格式: 英文)')
print(' 3.查找中文释义(输入格式: 中文)')
print(' 4.删除单词(输入格式: 英文)')
print(' 5.输出单词本中全部的单词')
print(' 请选择操作命令:')
if __name__ == "__main__":
t = TrieNode()
t.main()
flag = True # 设置循环变量
while flag:
print('请选择您要操作的功能!')
operator = input()
value = re.compile(r'^\d+$') # 利用正则表达式判断操作命令是否为整数
result = value.match(operator)
if result: # 防止出现输入的不是数字
operator = int(operator)
if operator == 1:
print('请输入要添加的单词!')
e_word, soundmark, *args = input().split()
t.add(e_word, soundmark, args)
print('是否继续添加单词:(Y/N)')
judge = input()
while judge == 'Y' or judge == 'y':
print('请输入要添加的单词!')
e_word, soundmark, *args = input().split()
t.add(e_word, soundmark, args)
print('是否继续添加单词:(Y/N)')
judge = input()
if judge == 'Y' or judge == 'y':
print('请输入要添加的单词!')
print('添加单词功能已退出!')
elif operator == 2:
if t.Chinese:
print('请输入要查询的英文单词!')
e_word = input()
t.query_English(e_word)
print('是否继续查询英文单词:(Y/N)')
judge = input()
while judge == 'Y' or judge == 'y':
e_word = input()
t.query_English(e_word)
print('是否继续查询英文单词: (Y/N)')
judge = input()
if judge == 'Y' or judge == 'y':
print('请输入要查询的英文单词!')
else:
print('词典中无单词!请先输入单词!')
print('查询已退出!')
elif operator == 3:
if t.Chinese:
print('请输入要查询的中文释义!')
C_word = input()
t.query_Chinese(C_word)
print('是否继续查询中文释义:(Y/N)')
judge = input()
while judge == 'Y' or judge == 'y':
C_word = input()
t.query_Chinese(C_word)
print('是否继续查询英文单词: (Y/N)')
judge = input()
if judge == 'Y' or judge == 'y':
print('请输入要查询的中文释义!')
else:
print('词典中无单词!请先输入单词!')
print('查询已退出!')
elif operator == 4:
if t.num == 0:
print('单词已经全部删除完毕,无法进行删除操作!')
print('该功能自动退出!')
break
print('请输入要删除的单词!')
del_word = input()
t.delete(del_word)
print('是否继续删除单词: (Y/N)')
judge = input()
while judge == 'Y' or judge == 'y':
del_word = input()
t.delete(del_word)
print('是否继续删除单词: (Y/N)')
judge = input()
if judge == 'Y' or judge == 'y':
print('请输入要删除的单词!')
print('删除操作已经退出!')
elif operator == 5:
t.show()
print('所有单词已全部输出!')
else:
print('请选择该词典中已有的功能!')
else:
print('请输入正确的操作数字!')
print('是否继续执行其他操作: (Y/N)')
judge_operator = input()
if judge_operator == 'Y' or judge_operator == 'y':
flag = True
elif judge_operator == 'N' or judge_operator == 'n':
flag = False
break
else:
print('请输入Y/N')
judge_operator = input()
print('1.添加单词 2.查找英文单词 3. 查找中文释义 4.删除单词 5. 展示所有词典所有单词', end='\n')
print('感谢您的使用!')
注意:
需要这个代码所处的文件夹中加入以下几个文本文件