Skip to content

四火的唠叨

一个纯正程序员的啰嗦

Menu
  • 所有文章
  • About Me
  • 关于四火
  • 旅行映像
  • 独立游戏
  • 资源链接
Menu

Trie 树和其它数据结构的比较

Posted on 12/10/201306/23/2019 by 四火

trie

Trie 树,又叫做前缀树或者是字典树,是一种有序的树。从空字符串的根开始,往下遍历到某个节点,确定了对应的字符串,也就是说,任意一个节点的所有子孙都具备相同的前缀。每一棵 Trie 树都可以被看做是一个简单版的确定有限状态的自动机(DFA,deterministic finite automaton),也就是说,对于一个任意给定的属于该自动机的状态 (①) 和一个属于该自动机字母表的字符 (②),都可以根据给定的转移函数 (③) 转到下一个状态去。其中:

  • ① 对于 Trie 树中的每一个节点都确定了一个自动机的状态;
  • ② 给定一个属于该自动机字母表的字符,在图中可以看到根据不同的字符形成的分支;
  • ③ 从当前节点进入下一层次节点的过程经过状态转移函数得出。

一个非常常见的应用就是搜索提示,在搜索框中输入搜索信息的前缀,如 “乌鲁”,提示 “乌鲁木齐”;再有就是输入法的联想功能,也是一样的道理。

和二叉搜索树(binary search tree)相比

二叉搜索树又叫做二叉排序树,它满足:

  • 任意节点如果左子树不为空,左子树所有节点的值都小于根节点的值;
  • 任意节点如果右子树不为空,右子树所有节点的值都大于根节点的值;
  • 左右子树也都是二叉搜索树;
  • 所有节点的值都不相同。

其实二叉搜索树的优势已经在与查找、插入的时间复杂度上了,通常只有 O(log n),很多集合都是通过它来实现的。在进行插入的时候,实质上是给树添加新的叶子节点,避免了节点移动,搜索、插入和删除的复杂度等于树的高度,属于 O(log n),最坏情况下整棵树所有的节点都只有一个子节点,完全变成一个线性表,复杂度是 O(n)。

Trie 树在最坏情况下查找要快过二叉搜索树,如果搜索字符串长度用 m 来表示的话,它只有 O(m),通常情况(树的节点个数要远大于搜索字符串的长度)下要远小于 O(n)。

我们给 Trie 树举例子都是拿字符串举例的,其实它本身对 key 的适宜性是有严格要求的,如果 key 是浮点数的话,就可能导致整个 Trie 树巨长无比,节点可读性也非常差,这种情况下是不适宜用 Trie 树来保存数据的;而二叉搜索树就不存在这个问题。

和 Hash 表相比

考虑一下 Hash 表键冲突的问题。Hash 表通常我们说它的复杂度是 O(1),其实严格说起来这是接近完美的 Hash 表的复杂度,另外还需要考虑到 hash 函数本身需要遍历搜索字符串,复杂度是 O(m)。在不同键被映射到 “同一个位置”(考虑 closed hashing,这 “同一个位置” 可以由一个普通链表来取代)的时候,需要进行查找的复杂度取决于这 “同一个位置” 下节点的数目,因此,在最坏情况下,Hash 表也是可以成为一张单向链表的(对于 Hash 冲突问题,请阅读 《Hash Collision DoS 问题》)。

Trie 树可以比较方便地按照 key 的字母序来排序(整棵树先序遍历一次就好了),这是绝大多数 Hash 表是不同的(Hash 表一般对于不同的 key 来说是无序的)。

在较理想的情况下,Hash 表可以以 O(1) 的速度迅速命中目标,如果这张表非常大,需要放到磁盘上的话,Hash 表的查找访问在理想情况下只需要一次即可;但是 Trie 树访问磁盘的数目需要等于节点深度。

很多时候 Trie 树比 Hash 表需要更多的空间,我们考虑这种一个节点存放一个字符的情况的话,在保存一个字符串的时候,没有办法把它保存成一个单独的块。Trie 树的节点压缩可以明显缓解这个问题,后面会讲到。

和后缀树相比

suffixTree

后缀树压缩存储了一段文本的所有可能的后缀,如给定单词 “banana”,可能的后缀包括:a、na、ana、nana、anana、banana 这几种,上图已经将所有可能全部放在树中表示出来了,“$” 表示一个后缀的结束,同时,还做到了尽量的分支压缩(分支压缩的说明在下文中也有提及)。对于给定长度为 n 的文本构造后缀树,它的定义要点包括:

  • 树有 n 个叶子节点,分别从 1 到 n 来命名;
  • 除了根节点,所有的非叶子节点至少有两个孩子;
  • 每一条边代表原文本的一个非空子串;
  • 不存在两条边以同一个字符开串标记且以同一个字符结尾;
  • 在从根节点到叶子 i 的路径上,连接所有字符串标记形成的字符串,都表示了一个原文本的后缀子串。

构造后缀树根据文本长度需要消耗线性的时间。和Trie 树相比,后缀树做到了用空间换时间,考虑全文搜索的情况,后缀树把所有可能的后缀子串都索引化了,就避免了 Trie 树深度遍历整棵树的过程。在算法题中许多关于 “前缀子串”问题上,我们经常使用 Trie 树来求解,但是如果问题仅仅涉及 “子串”,往往选用后缀树;另外,还有一个重要的使用在文本压缩算法上,通过后缀树可以找到重复率高的文本,实现重复文本抽取,放到字典映射表中去。

Trie 树的改进

1. 按位 Trie 树(Bitwise Trie):原理上和普通 Trie 树差不多,只不过普通 Trie 树存储的最小单位是字符,但是 Bitwise Trie 存放的是位而已。位数据的存取由 CPU 指令一次直接实现,对于二进制数据,它理论上要比普通 Trie 树快。

2. 节点压缩。

  • ① 分支压缩:对于稳定的 Trie 树,基本上都是查找和读取操作,完全可以把一些分支进行压缩。例如,前图中最右侧分支的 inn 可以直接压缩成一个节点 “inn”,而不需要作为一棵常规的子树存在。Radix 树就是根据这个原理来解决 Trie 树过深问题的。
  • ② 节点映射表:这种方式也是在 Trie 树的节点可能已经几乎完全确定的情况下采用的,针对 Trie 树中节点的每一个状态,如果状态总数重复很多的话,通过一个元素为数字的多维数组(比如 Triple Array Trie)来表示,这样存储 Trie 树本身的空间开销会小一些,虽说引入了一张额外的映射表。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

×Scan to share with WeChat

你可能也喜欢看:

  1. LeetCode 算法题目解答汇总
  2. LeetCode 题目解答——155~226 题
  3. LeetCode 数据库十道题解答
  4. 常见分布式应用系统设计图解(四):输入建议系统
  5. 建立动态规划状态转移方程的练习

1 thought on “Trie 树和其它数据结构的比较”

  1. Anonymous says:
    03/18/2015 at 6:12 PM

    trie 树是 radix-search tree 的一种,此类树最有用的是它的“radix” 性质,即:对于树中任意一个节点,以该节点为根的子树中的所有节点的键值,都以该节点键值为前缀。
    如果只看重查找性能,一般不会选择 trie 树。

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

订阅·联系

四火,啰嗦的程序员一枚,现居西雅图

Amazon Google Groovy Hadoop Haskell Java JavaScript LeetCode Oracle Spark 互联网 亚马逊 前端 华为 历史 同步 团队 图解笔记 基础设施 工作 工作流 工具 工程师 应用系统 异步 微博 思考 技术 数据库 曼联 测试 生活 眼界 程序员 管理 系统设计 缓存 编程范型 美股 英语 西雅图 设计 问题 面向对象 面试

分类

  • Algorithm and Data Structure (30)
  • Concurrency and Asynchronization (6)
  • System Architecture and Design (43)
  • Distributed System (18)
  • Tools Frameworks and Libs (13)
  • Storage and Data Access (8)
  • Front-end Development (33)
  • Programming Languages and Paradigms (55)
  • Testing and Quality Assurance (4)
  • Network and Communication (6)
  • Authentication and Authorization (6)
  • Automation and Operation Excellence (13)
  • Machine Learning and Artificial Intelligence (6)
  • Product Design (7)
  • Hiring and Interviews (14)
  • Project and Team Management (14)
  • Engineering Culture (17)
  • Critical Thinking (25)
  • Career Growth (57)
  • Life Experience and Thoughts (45)

推荐文章

  • 聊一聊分布式系统中的时间
  • 谈谈分布式锁
  • 常见分布式系统设计图解(汇总)
  • 系统设计中的快速估算技巧
  • 从链表存在环的问题说起
  • 技术面试中,什么样的问题才是好问题?
  • 从物理时钟到逻辑时钟
  • 近期面试观摩的一些思考
  • RSA 背后的算法
  • 谈谈 Ops(汇总 + 最终篇):工具和实践
  • 不要让业务牵着鼻子走
  • 倔强的程序员
  • 谈谈微信的信息流
  • 评审的艺术——谈谈现实中的代码评审
  • Blog 安全问题小记
  • 求第 K 个数的问题
  • 一些前端框架的比较(下)——Ember.js 和 React
  • 一些前端框架的比较(上)——GWT、AngularJS 和 Backbone.js
  • 工作流系统的设计
  • Spark 的性能调优
  • “残酷” 的事实
  • 七年工作,几个故事
  • 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(汇总)
  • 一道随机数题目的求解
  • 层次
  • Dynamo 的实现技术和去中心化
  • 也谈谈全栈工程师
  • 多重继承的演变
  • 编程范型:工具的选择
  • GWT 初体验
  • java.util.concurrent 并发包诸类概览
  • 从 DCL 的对象安全发布谈起
  • 不同团队的困惑
  • 不适合 Hadoop 解决的问题
  • 留心那些潜在的系统设计问题
  • 再谈大楼扔鸡蛋的问题
  • 几种华丽无比的开发方式
  • 我眼中的工程师文化
  • 观点的碰撞
  • 谈谈盗版软件问题
  • 对几个软件开发传统观点的质疑和反驳
  • MVC 框架的映射和解耦
  • 编程的未来
  • DAO 的演进
  • 致那些自嘲码农的苦逼程序员
  • Java 多线程发展简史
  • 珍爱生命,远离微博
  • 网站性能优化的三重境界
  • OSCache 框架源码解析
  • “ 你不适合做程序员”
  • 画圆画方的故事

近期评论

  • Ticket: TRANSACTION 1.922915 BTC. Go to withdrawal >> https://yandex.com/poll/enter/BXidu5Ewa8hnAFoFznqSi9?hs=20bd550f65c6e03103876b28cabc4da6& on 倔强的程序员
  • panshenlian.com on 初涉 ML Workflow 系统:Kubeflow Pipelines、Flyte 和 Metaflow
  • panzhixiang on 关于近期求职的近况和思考
  • Anonymous on 闲聊投资:亲自体验和护城河
  • 四火 on 关于近期求职的近况和思考
  • YC on 关于近期求职的近况和思考
  • mafulong on 常见分布式基础设施系统设计图解(四):分布式工作流系统
  • 四火 on 常见分布式基础设施系统设计图解(八):分布式键值存储系统
  • Anonymous on 我裸辞了
  • https://umlcn.com on 资源链接
© 2025 四火的唠叨 | Powered by Minimalist Blog WordPress Theme