首页 > 其他分享 >字典树

字典树

时间:2023-04-12 20:13:53浏览次数:33  
标签:obj self 字符串 input root 字典

目录

字典树(Trie)

字典树(Trie),也称为“前缀树”,是一种特殊的树状数据结构,对于解决字符串相关问题非常有效。它能够提供快速检索,主要用于搜索字典中的单词,在搜索引擎中自动提供建议,甚至被用于 IP 的路由。

它是一种多叉树,即把前缀相同的字符串合并在一起根节点默认不存储字符

这里,我们用一棵字典树来展示:

image

可以发现,这棵字典树用边来代表字母,而从根结点到树上某一结点的路径就代表了一个字符串

例如,\(1\to4\to 8\to 12\) 表示的就是字符串 \(caa\)。

可以看出 Trie树 本质就是利用字符串之间的公共前缀,将重复的前缀合并在一起,根据这个特点,我们可以通过查询字符的前缀,快速地查询到具有相同前缀的单词。

\(trie\) 的结构非常好懂,我们用 \(\delta(u,c)\) 表示结点 \(u\) 的 \(c\) 字符指向的下一个结点,或着说是结点 \(u\) 代表的字符串后面添加一个字符 \(c\) 形成的字符串的结点。

有时需要标记插入进 \(trie\) 的是哪些字符串,每次插入完成时,在这个字符串所代表的节点处打上标记即可。

力扣上的典型应用如下:

序号 题目
1 642. 设计搜索自动补全系统
2 1268. 搜索推荐系统

应用

应用1:Leetcode.642

题目

642. 设计搜索自动补全系统

题目分析

只要涉及单词自动补全相关的,都可以使用字典树作为底层数据结构。

算法思路:

  • 使用字典树 trie 记录所有的输入字符串,对于每一个子节点我们使用一个 \(hash\) 表记录字符串与出现次数;

  • 使用一个 _input 作为输入字符的缓冲区,对于每一个输入的字符:

    • 如果它不是结束符("#"),则在字典树上进行一次查询,得到当前字符串对应的结果集 \(S\);

    • 如果它是结束符("#"),则将缓冲区中的字符拼接为一个字符串,并保存到字典树上,同时,将该字符串出现的次数加 \(1\);

  • 将结果集 \(S\) 中的字符串,按照出现次数与字母顺序排序,取前 \(3\) 个结果,作为答案返回;

代码实现

from typing import List, Dict


class Node(object):
    def __init__(self):
        self.sentences = dict()
        self.children = dict()

    def update_times(self, sentence: str, times: int = 1):
        """ 更新次数 """
        self.sentences.update({sentence: self.sentences.get(sentence, 1) + times})


class Trie(object):
    def __init__(self):
        self.root = Node()
        self._input = list()

    def input(self, content: str):
        self._input.append(content)

        sentence = "".join(self._input)
        if content.endswith("#"):
            self._input = list()
            sentence = sentence[:-1]
            self.add(sentence)
            return list()

        result = self._search(sentence)
        return self._top_k(result)

    def add(self, sentence: str, times: int = 1):
        root = self.root
        for char in sentence:
            if char not in root.children:
                root.children[char] = Node()
            root = root.children[char]
            root.update_times(sentence, times)
        return

    def _search(self, sentence: str) -> Dict[str, int]:
        root = self.root
        for char in sentence:
            if char not in root.children:
                return dict()
            root = root.children[char]
        return root.sentences

    def _top_k(self, sentences: Dict[str, int], k: int = 3):
        result = sorted(sentences.items(), key=lambda x: (-x[1], x[0]))
        return [candidate[0] for candidate in result[:k]]


class AutocompleteSystem:
    def __init__(self, sentences: List[str], times: List[int]):
        self.trie = Trie()
        for i in range(len(sentences)):
            self.trie.add(sentence=sentences[i], times=times[i])

    def input(self, c: str) -> List[str]:
        return self.trie.input(content=c)


if __name__ == "__main__":
    def test_case1():
        sentences = ["i love you", "island", "iroman", "i love leetcode"]
        times = [5, 3, 2, 2]
        obj = AutocompleteSystem(sentences=sentences, times=times)

        print(obj.input("i"))
        print(obj.input(" "))
        print(obj.input("a"))
        print(obj.input("#"))


    def test_case2():
        sentences = ["i love you", "island", "iroman", "i love leetcode"]
        times = [5, 3, 2, 2]
        obj = AutocompleteSystem(sentences=sentences, times=times)

        print(obj.input("i"))
        print(obj.input(" "))
        print(obj.input("a"))
        print(obj.input("#"))
        print(obj.input("i"))
        print(obj.input(" "))
        print(obj.input("a"))
        print(obj.input("#"))
        print(obj.input("i"))
        print(obj.input(" "))
        print(obj.input("a"))
        print(obj.input("#"))


    test_case2()

应用2:Leetcode.1268

题目

1268. 搜索推荐系统

分析

算法实现思路:

  • 使用一个字典树 trie 记录所有的输入字符串,对于每一个子节点,使用一个列表 words 记录以当前路径为前缀的所有字符串;
  • 对于每一个输入的字符串 word,将其添加到字典树 trie 中,添加的过程:
    • 字典树 trie 的根节点作为起点,遍历字符串 word 中的每一个字符;
    • 如果当前字符 char ,如果它不是当前节点的子节点,则创建一个新的节点;
    • 将当前字符串保存到子节点中
  • 遍历 products 中的每一个单词,对于每一个单词,查找排序后的前 3 的个匹配结果。

代码实现

from typing import List


class TrieNode:
    def __init__(self):
        # 用Hash表保存子节点
        self.child = dict()
        # 保存当前节点的值
        self.words = list()


class Trie(object):
    def __init__(self):
        self.root = TrieNode()

    def add(self, word: str):
        root = self.root
        for char in word:
            if char not in root.child:
                root.child[char] = TrieNode()
            root = root.child[char]
            root.words.append(word)

    def get_top_k(self, target: str):
        result = list()
        root = self.root
        flag = False
        for char in target:
            if flag or char not in root.child:
                result.append(list())
                flag = True
            else:
                root = root.child[char]
                root.words.sort()
                result.append(root.words[:3])

        return result


class Solution:
    def suggestedProducts(self, products: List[str], searchWord: str) -> List[List[str]]:
        trie = Trie()
        for word in products:
            trie.add(word)

        return trie.get_top_k(searchWord)

总结

Trie 树构建时,需要遍历所有的字符串,因此时间复杂度为所有字符串长度总和 n,时间复杂度为 O(n)。

但是,Trie 树的查找效率很高,如果字符串长度为 k,那么时间复杂度 为 O(k)。

Trie 是一种以空间换时间的结构,当字符集较大时,会占用很多空间,同时如果前缀重合较少,空间会占用更多。所以其比较适合查找前缀匹配。

可以看到, Trie 树的内存消耗还是比较大的,它对要处理的字符串有极其严苛的要求:

  • 字符串中包含的字符集不能太大,如果字符集太大,存储空间就会浪费严重,即使进行优化,也会牺牲查询、插入效率;

  • 字符串的前缀重合要比较多,否则空间消耗会比较大;

  • Trie 树使用了指针进行存储,对缓存不友好;

  • 在工程中,可能要我们自行实现一个 Trie 树 ,这会让工程变得复杂。

所以,对于在一组字符串中查找字符串的问题,在工程中一般更倾向于使用散列表(Hash)或者红黑树(RBT)。

字典树的应用

Trie 树比较适合这样的应用场景:当我们需要使用前缀快速查找相关的字符串的时候,例如,搜索引擎的自动补全功能、输入法的自动补全功能、IDE代码编辑器自动补全功能、浏览器网址输入的自动补全功能等。

Tire 树的典型应用场景:

  • 串的快速检索

    给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。在这道题中,我们可以用数组枚举,用哈希,用字典树,先把熟词建一棵树,然后读入文章进行比较, 这种方法效率是比较高的。

  • “串”排序

    给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出。用字典树进行排序, 采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。 对这棵树进行先序遍历即可。

  • 最长公共前缀

    对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数, 于是,问题就转化为求公共祖先的问题。

参考:

标签:obj,self,字符串,input,root,字典
From: https://www.cnblogs.com/larry1024/p/17309973.html

相关文章

  • 【图论之拓扑排序】剑指 Offer II 114. 外星文字典
    剑指OfferII114.外星文字典讲解传送门constintN=26,M=N*N;classSolution{public:inth[N],e[M],ne[M],idx=0;boolst[N];intin[N],cnt=0;//上面三行要写在classSolution内部,不然每次调用不会清空voidadd(inta,intb){......
  • oracle 常用数据字典表说明
    常用数据字典表数据字典表是oracle存放数据库信息的地方,其用途是用来描述数据的。数据字典表分类数据字典主要可分为四部分(1)内部RDBMS表:x$,用于跟踪内部数据库信息,维持DB的正常运行。是加密命名的,不允许sysdba以外的用户直接访问,显示授权不被允许。(2)数据字典表:$,如tab......
  • C++ 按照字典序实现combination
    C++按照字典序实现combination引言C++STL提供了permutation相关的函数(std::next_permutation和std::prev_permutation),但是没有提供combination相关的函数,本文将基于字典序的方法实现一个combination相关的函数。算法回顾1.permutation用法C++的permutation是基于字典序实......
  • SQL基础操作_3_数据字典(涵盖SQL Server、Oracle、Mysql常见系统数据字典)
    目录数据库元数据查询7.5.1列出模式中所有的表7.5.2列出所有的数据库7.5.3列出给定表的基本信息7.5.4列出给定表的索引信息7.5.5列出给定表的主键、外键约束7.5.6列出给定表的外键引用7.5.7列出给定表的检查约束7.5.8列出给定表的默认约束7.5.9列出给定表的所有约束7.5.10......
  • HJ45_名字的漂亮度_贪心(字符串字符次数排序)_附:字典排序
    思路:每个字母都有一个漂亮度1-26。每个字母漂亮度不相同忽略大小写,字符串漂亮度是字母漂亮度总和。取次数最多的字符漂亮度最大,其他依次次大。 #贪心。先排序从大到小,后计算整体漂亮度。从局部最优到整体最优,为贪心算法。  代码:1fromcollectionsimportCounter2......
  • C# 委托与字典
    privatedelegateintdelStepDoSomething(objectobj=null);privatestaticDictionary<int,delStepDoSomething>stepDoSomething=newDictionary<int,delStepDoSomething>();privatestaticDictionary<int,Func<object,int>>step......
  • 读取配置文件的配置字典数据(字典数据包含中文)
        项目有时为了方便配置数据字典,会创建类似于“test=测试”的key-value形式的数据字典,在项目启动时便缓存该字典数据,方便后续使用;但是该字典有时候又存在中文,在加载之后会出现乱码问题,便需要对加载的数据进行特殊处理。publicclassConfigUtils{/***加......
  • 关于python中使用json.loads()将字符串数据转换成字典
    在json模块中,我们可以经常会用到load()与loads(),其中两者的区别如下json.load()从json文件中读取数据转抱为dict类型json.loads()将str类型的数据转换为dict类型这里笔者主要说明json.loads()的用法,将字符串转转换成字典,如下str2dict.py脚本内容:importjsonJsonStr='''{......
  • 关于python中使用json.load()从json文件中读取数据转换成字典
    在json模块中,我们可以经常会用到load()与loads(),其中两者的区别如下json.load()从json文件中读取数据转抱为dict类型json.loads()将str类型的数据转换为dict类型这里笔者主要说明json.load()的用法,举例说明,如下有一json文件,ip-ranges.json,内容如下:这里我们将使用json.load(......
  • HJ66 配置文件恢复_字典_字符串
    思路:1、把命令和执行对录入一字串字典和二字串字典2、取字典的可以与输入对比3、为了保证唯一性,用c常数增加1来判断是否唯一。4、最后根据c值统一打印输出1importsys2a=[]3forlineinsys.stdin:4a.append(line.strip().split())5#print(a)6d1={"rese......