基于Trie树的一个小词典

260 阅读9分钟

什么是Trie树?

Trie树, 又叫字典树、前缀树、单词查找树, 呈多叉树结构, 是一种能够高效存储和查找字符串的数据结构。

image.png

上述图是一棵Trie树, 有abate、about、abeve、have、you、love、to这些单词。

上述图可以得出Trie树的简单性质:

1,根节点不包含字符,除根节点意外每个节点只包含一个字符。 2,从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。 3,每个节点的所有子节点包含的字符串不相同。

在使用这个数据结构时, 通常会给每个输入的字符的最后一个节点加上标记, 用来说明此处是否构成了一个单词(或者关键字)。

Trie树的优缺点

优点:

  1. 查询和插入的效率都很高,都是O(m),m为待查询和待插入的字符串长度。

  2. Trie树的查询不会出现冲突。

  3. Trir树可以对关键字按字典序排序。

缺点:

  1. 当hash函数很好时, Trie的查找效率会低于哈希搜索。

  2. 空间占用量大。

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('感谢您的使用!')

注意:

需要这个代码所处的文件夹中加入以下几个文本文件

image.png

参考资料: AcWing 835. 如何理解单(双)链表,Trie树和堆中的idx? - AcWing