当前位置:   article > 正文

Python算法题集_实现 Trie [前缀树]

Python算法题集_实现 Trie [前缀树]

本文为Python算法题集之一的代码示例

题208:实现 Trie (前缀树)

1. 示例说明

  • Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。

    请你实现 Trie 类:

    • Trie() 初始化前缀树对象。
    • void insert(String word) 向前缀树中插入字符串 word
    • boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false
    • boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false

    示例:

    输入
    ["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
    [[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
    输出
    [null, null, true, false, true, null, true]
    
    解释
    Trie trie = new Trie();
    trie.insert("apple");
    trie.search("apple");   // 返回 True
    trie.search("app");     // 返回 False
    trie.startsWith("app"); // 返回 True
    trie.insert("app");
    trie.search("app");     // 返回 True
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    提示:

    • 1 <= word.length, prefix.length <= 2000
    • wordprefix 仅由小写英文字母组成
    • insertsearchstartsWith 调用次数 总计 不超过 3 * 104

2. 题目解析

- 题意分解

  1. 本题是为自动补完、拼写检查等创造一个高效率的检索类
  2. 基本的设计思路迭代单词,每层用字典保存,同时还需要保存单词结尾信息【search检测结尾、startWith不检测】

- 优化思路

  1. 通常优化:减少循环层次

  2. 通常优化:增加分支,减少计算集

  3. 通常优化:采用内置算法来提升计算速度

  4. 分析题目特点,分析最优解

    1. 可以尝试使用默认字典defaultdict

    2. 本题都是小写字母,因此26个元素的字典就可以保存一个层级

    3. 所有单词字符都是ASCII码,Ord值都在0-127,因此128个元素的字典可以正常使用【超时测试用例,需要128一层】

    4. 可以考虑将单词结尾信息保存在字典中,用一个单词中不会出现的字符即可,比如’#’


- 测量工具

  • 本地化测试说明:LeetCode网站测试运行时数据波动很大【可把页面视为功能测试】,因此需要本地化测试解决数据波动问题
  • CheckFuncPerf(本地化函数用时和内存占用测试模块)已上传到CSDN,地址:Python算法题集_检测函数用时和内存占用的模块
  • 本题本地化超时测试用例自己生成,详见章节【最优算法】,需要安装和部署**NLTK**

3. 代码展开

1) 标准求解【定义数据类+默认字典】

使用默认字典,定位专门的数据类,使用类属性保存单词结尾信息

页面功能测试,马马虎虎,超过33%在这里插入图片描述

import CheckFuncPerf as cfp

class prenode:
 def __init__(self):
     self.chars = defaultdict(int)

class Trie_base:
 def __init__(self):
     self.node = prenode()
     self.bEnd = False

 def searchPrefix(self, prefix):
     tmpNode = self
     for achar in prefix:
         ichar = ord(achar) - ord("a")
         if tmpNode.node.chars[ichar] == 0:
             return None
         tmpNode = tmpNode.node.chars[ichar]
     return tmpNode

 def insert(self, word):
     tmpNode = self
     for achar in word:
         ichar = ord(achar) - ord("a")
         if tmpNode.node.chars[ichar] == 0:
             tmpNode.node.chars[ichar] = Trie_base()
         tmpNode = tmpNode.node.chars[ichar]
     tmpNode.bEnd = True

 def search(self, word):
     node = self.searchPrefix(word)
     return node is not None and node.bEnd

 def startsWith(self, prefix):
     return self.searchPrefix(prefix) is not None

tmpTrie = Trie_base()
result = cfp.getTimeMemoryStr(testTrie, tmpTrie, actions)
print(result['msg'], '执行结果 = {}'.format(result['result']))

# 运行结果
函数 testTrie 的运行时间为 7127.62 ms;内存使用量为 373008.00 KB 执行结果 = 99
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

2) 改进版一【初始化字典+无额外类】

将字典数据和单词结尾信息都保存在节点类中,创建类同时初始化字典的128个元素【按题意只需26,本类已经按超时测试改写】

页面功能测试,马马虎虎,超过65%在这里插入图片描述

import CheckFuncPerf as cfp

class Trie_ext1:
 def __init__(self):
     self.data = [None] * 128
     self.bEnd = False

 def searchPrefix(self, prefix):
     tmpnode = self
     for achar in prefix:
         ichar = ord(achar)
         if not tmpnode.data[ichar]:
             return None
         tmpnode = tmpnode.data[ichar]
     return tmpnode

 def insert(self, word):
     tmpnode = self
     for achar in word:
         ichar = ord(achar)
         if not tmpnode.data[ichar]:
             tmpnode.data[ichar] = Trie_ext1()
         tmpnode = tmpnode.data[ichar]
     tmpnode.bEnd = True

 def search(self, word):
     tmpnode = self.searchPrefix(word)
     return tmpnode is not None and tmpnode.bEnd

 def startsWith(self, prefix):
     return self.searchPrefix(prefix) is not None

tmpTrie = Trie_ext1()
result = cfp.getTimeMemoryStr(testTrie, tmpTrie, actions)
print(result['msg'], '执行结果 = {}'.format(result['result']))

# 运行结果
函数 testTrie 的运行时间为 5857.32 ms;内存使用量为 793700.00 KB 执行结果 = 99
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

3) 改进版二【字典保存结尾信息+无额外类】

在字典中保存单词结尾信息,将字典数据保存在节点类中,创建类时不初始化字典

页面功能测试,性能卓越,超越96%在这里插入图片描述

import CheckFuncPerf as cfp

class Trie_ext2:
 def __init__(self):
     self.tree = {}

 def insert(self, word):
     tree = self.tree
     for achar in word:
         if achar not in tree:
             tree[achar] = {}
         tree = tree[achar]
     tree["#"] = "#"

 def search(self, word):
     tree = self.tree
     for achar in word:
         if achar not in tree:
             return False
         tree = tree[achar]
     return "#" in tree

 def startsWith(self, prefix):
     tree = self.tree
     for achar in prefix:
         if achar not in tree:
             return False
         tree = tree[achar]
     return True

tmpTrie = Trie_ext2()
result = cfp.getTimeMemoryStr(testTrie, tmpTrie, actions)
print(result['msg'], '执行结果 = {}'.format(result['result']))

# 运行结果
函数 testTrie 的运行时间为 1670.38 ms;内存使用量为 146692.00 KB 执行结果 = 99
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

4. 最优算法

根据本地日志分析,最优算法为第3种方式【字典保存结尾信息+无额外类】Trie_ext2

本题大概有以下结论:

  1. 独立的变量,如果能保存在字典结构里,减少独立的变量数,可以提升性能
  2. 数据集的默认初始化可能会扩大内存使用,同时数据量过大、内存过大也拖累性能
import random
from nltk.corpus import words
word_list = list(words.words())
def testTrie(aTrie, actions):
    for act in actions:
        if act[0]==1:   # insert
            aTrie.insert(act[1])
        elif act[0]==2: # search
            aTrie.search(act[1])
        elif act[0]==3: # startsWith
            aTrie.startsWith(act[1])
    return 99
import random
actions = []
iLen = 1000000
for iIdx in range(iLen):
    actions.append([random.randint(1, 3), random.choice(word_list)])
tmpTrie = Trie_base()
result = cfp.getTimeMemoryStr(testTrie, tmpTrie, actions)
print(result['msg'], '执行结果 = {}'.format(result['result']))
tmpTrie = Trie_ext1()
result = cfp.getTimeMemoryStr(testTrie, tmpTrie, actions)
print(result['msg'], '执行结果 = {}'.format(result['result']))
tmpTrie = Trie_ext2()
result = cfp.getTimeMemoryStr(testTrie, tmpTrie, actions)
print(result['msg'], '执行结果 = {}'.format(result['result']))

# 算法本地速度实测比较
函数 testTrie 的运行时间为 7127.62 ms;内存使用量为 373008.00 KB 执行结果 = 99
函数 testTrie 的运行时间为 5857.32 ms;内存使用量为 793700.00 KB 执行结果 = 99
函数 testTrie 的运行时间为 1670.38 ms;内存使用量为 146692.00 KB 执行结果 = 99
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

5. 相关资源

本文代码已上传到CSDN,地址:**Python算法题源代码_LeetCode(力扣)_**实现Trie(前缀树)

一日练,一日功,一日不练十日空

may the odds be ever in your favor ~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/148451
推荐阅读
相关标签
  

闽ICP备14008679号