CloudNativeEra
  • Introduction
  • 名词解释
  • Computer Science
    • Computer Organization
      • CPU
      • 二进制、电路、加法器、乘法器
      • 编译、链接、装载
      • 存储器
      • IO
    • Operating System
      • 操作系统基础知识
      • 系统初始化
      • 进程管理
      • Everything about Memory
      • 文件系统
      • 并行编程
      • Linux
        • CPU
        • IO 多路复用
        • DMA IO and Linux Zero Copy
    • Computer Network
      • 网络相关命令
      • 评估系统的网络性能
      • 网络抓包
      • Linux 最多支撑的 TCP 连接
      • 网络虚拟化
      • DHCP 工作原理
    • Data Structure and Algorithm
      • 题目列表
      • Summarize
        • 方法总结
        • 二分思想
        • 树的演化
        • 算法思想总结
      • Data Structure
        • Data Struct - Array
        • Tree
        • Heap
        • Hash
        • 字符串
      • Algorithm
        • Sorting Algorithm
        • 查找
        • 贪心算法
        • 动态规划
        • 位运算
      • Practice Topics
        • Data Struct in SDK
        • Topic - Tree
        • Topic - Graph
        • Topic - 滑动窗口
        • 剑指 Offer 题解
    • 并发编程
      • 并发模式
      • 并发模型
  • 系统设计
    • 软件设计
      • 软件架构
      • 编程范式
      • 系统设计题
      • 设计原则
      • 计算机程序的构造和解释 SICP
    • 领域驱动设计
      • 应用:在线请假考勤管理
      • 应用: library
    • 微服务与云原生
      • Designing and deploying microservices
      • 容器技术
      • Docker
      • Etcd
      • Kubernetes
        • Kubernetes - Mapping External Services
      • Istio
      • 监控
    • 分布式系统
      • 分布式理论
      • 分布式事务
    • 后端存储设计
      • 缓存设计
      • 数据库架构设计
    • CI/CD
    • 设计最佳实践
    • 测试
    • 安全
    • 综合
      • 开发实践
      • 分布式锁
      • 分布式计数服务
      • 弹幕系统设计
      • 消息队列设计
      • 分布式ID生成算法
      • 限流设计
      • 网关设计
      • 通用的幂等设计
      • 分布式任务调度
        • Timer
        • ScheduledExecutorService
        • Spring Task
        • Quartz
      • 交易系统
      • 权限设计
  • 编程语言
    • 编程语言
    • C & C++
    • Java
      • JVM
        • JVM Bytecode
      • Java 核心技术
      • Java 8 新特性
      • Java 集合框架
      • Java NIO
      • 并发编程
        • 线程生命周期与线程中断
        • 三个线程交替打印
        • 两个线程交替打印奇偶
        • 优雅终止线程
        • 等待通知机制
        • 万能钥匙:管程
        • 限流器
        • 无锁方案 CAS
    • Java 源码阅读
      • Unsafe
      • 异步计算 Future
      • Java Queue
      • CoalescingRingBuffer 分析
      • Java Collections
        • PriorityQueue 分析
        • HashMap 分析
        • TreeMap
    • Golang
    • Python
  • 框架/组件/类库
    • Guava
      • Guava Cache
      • Guava EventBus
    • RxJava
    • Apache MINA
    • Netty
      • 网络 IO 模型
      • Netty 生产问题
    • Apache Tomcat
    • MyBatis
    • 限流框架
    • Spring Framework
      • Spring Core
      • Spring 事务管理
    • Spring Boot
    • Spring Cloud
      • Feign & OpenFeign
      • Ribbon
      • Eurake
      • Spring Cloud Config
    • FixJ
    • Metrics
    • Vert.x
  • 中间件
    • Redis
      • Redis 基础
        • Redis 数据结构设计与实现
        • Redis 高性能网络模型
      • Redis checklist
      • 应用案例 - Redis 数据结构
      • 应用案例 - Redis 缓存应用
      • 应用案例 - Redis 集群
      • Redis 客户端
      • Redis 生产案例
        • [译] 在 Redis 中存储数亿个简单键值对
    • MySQL
      • MySQL 基础
      • MySQL Index
      • MySQL Transaction
      • MySQL 优化
      • MySQL 内核
      • MySQL Command
      • MySQL Checklist
      • MySQL Analysis Tool
      • 实现 MySQL
    • State Machine
    • 数据库连接池
    • MQ
      • 高性能内存队列 Disruptor
      • Kafka
      • Pulsar
      • RocketMQ
        • Broker 的设计与实现
      • NSQ
  • 实际案例
    • 线上 Case
      • Request Aborted
      • MySQL - Specified key was too long
      • Java 应用 CPU 100% 排查优化
      • 频繁 GC 导致的 Java 服务不响应
      • 导出优化
  • 大数据
    • 流计算
    • Flink
  • 其他
    • 工具
    • 读书
      • 设计数据密集型应用
      • 实现领域驱动设计
      • 精通比特币
      • 提问的智慧
    • 论文
    • 工程博客
    • 阅读源码
    • 面试
      • 如何在最短的时间里对对方有个全面的了解
    • 分享
    • 软技能
    • Todo
  • Blog
    • #算法
      • 查找
      • 位运算
      • 树
    • #架构
      • 1- 通信
    • Design & Dev & Opt
      • High Performance Data structure Design
  • Tiny Project
    • A Simple WeChat-like Instant Messaging Platform
由 GitBook 提供支持
在本页
  • 树的数学性质
  • 为什么会有那么多特殊化的树

这有帮助吗?

  1. Computer Science
  2. Data Structure and Algorithm
  3. Summarize

树的演化

这是训练营的第二周,也持续的做了几十道算法题,每道题自己都竭尽全力的去找尽可能多的解法,且每个解法都实现出来。此前一直对树这个数据结构理解的不是很彻底很到位,以及围绕树相关的算法也感觉很飘忽很神秘,所以本周就萌生了全方位总结树这个数据结构以及和它相关的算法。

为什么需要树?当在面临大量的输入数据时,线性表的访问时间就变的很慢了,有没有一种数据结构可以加速访问呢,树就是其中一种,树可以将大部分的操作都优化到 O(logn) 的时间复杂度,可以大大提升效率;此外,我们在日常生活中无时无刻不在使用树,比如想中午吃什么的时候,我们不自觉的构建了一颗决策树,操作系统的文件系统等;(在这篇总结中,不再罗列概念)

树的数学性质

有时候了解更多关于树的数学性质有助于解决很多问题,所有的数学性质是直接可以使用的

  1. 高度为 h 的二叉树至多有 2^h 个叶子结点

  2. 高度为 h≥0 的二叉树至少有 h+1 个结点

  3. 高度不超过 h(≥0) 的二叉树至多有 2^(h+1)-1 个结点

  4. 含有 n≥1 个结点的二叉树的高度至多为 n-1

  5. 含有 n≥1 个结点的二叉树的高度至少为 logn,因此其高度为 Ω(logn)

  6. 高度为 h 的满二叉树,共有 2^(h+1)-1 个结点

  7. 满二叉树共有 2^h 个叶子

  8. 满二叉树共有 2^h-1 个内结点,内结点个数比叶子少1

  9. 完全二叉树第 h 层的从左到右第 k 个结点的编号为 2^h + k - 1

  10. 完全二叉树叶子个数或者和内结点个数相等或者多1

  11. 完全二叉树通过本结点的编号可以快速得到父结点、左右孩子的编号;编号为i的父节点为 i/2, 左子节点为 2*i, 右子节点为 2*i + 1(这个性质在堆中得到了很好的应用)

  12. etc.

为什么会有那么多特殊化的树

这是本次总结想要好好分析的。

树的一般化定义是:一棵树是一些节点的集合。这个集合可以是空;若非空,一个树由根节点以及0个或多个非空子树组成,每一棵子树都和根有一条有向边所连接;一颗树是N个节点和N-1条边的集合。从树的定义中,我们可以推论出很多树的数学性质,例如上面描述的。

树的演化过程,其实是从一般到特殊化的过程,在演化中出现各种特殊的树,来解决不同种类的问题。但是一颗一般化的树,在一些特殊的场景下,并不能为我们解决很多问题

  • 从二叉树开始:在树中如何利用二分查找的思想

二叉树是一颗每个节点最多有两个孩子的树,分别是左孩子和右孩子。可以看出,我们在树的基础上做了限制,二叉树为我们提供了每次二分的可能,也简化了树以及各种操作的实现。在使用这颗二叉树的时候,我们可以尽可能的去构造这颗树,但是怎么解决快速查找的问题,尤其是在有大量数据存在时?如果查找的时间复杂度还不如链表,那就没有意义了。

我们知道在一个排好序的数组中进行二分查找的时间复杂度是 O(logn), 是非常高效的(40亿量级的数据,只需要32次左右的查找);所以将二分查找的思想应用在二叉树中,就是一颗二叉查找树,它是这么一棵树:在树中的任意一个节点,其左子树中的每个节点,都要小于这个节点的值,其右子树的每个节点,都要大于这个节点的值。可见,二叉查找树在二叉树的基础上又做了限制,更加特殊化了。

为什么二叉查找树的查找效率高?我们首先来分析一颗完全二叉查找树,这是一种比较理想的状态,因为树的高度是 logn, 也是二叉查找树所有形态中高度最小的树;我们试着来分析一下:

// 对于一颗包含 n 个节点完全二叉树
// 1. 除了最后一层外,每一层的节点是上一层节点个数的2倍; 一般的,第一层是1个节点,第二层是2个节点,第三层是4个节点,以此类推
// 2. 最后一层节点的个数在 1 ~ 2^(L-1), 其中 L 表示最大层数
// 综上可知,n 满足
n <= 1 + 2 + 4 + ... + 2^(L-2) + 2^(L-1)
n >= 1 + 2 + 4 + ... + 2^(L-2) + 1

=> log(n+1) <= L <= log(n) + 1

可知,L 的最大值是 log(n) + 1, 高度h最大是 log(n); 所以在查找某个元素时,最大查找次数是 log(n), 故而时间复杂度是 O(logn), 很高效;同理,插入、删除操作时间复杂度也是 O(logn)

上面我们说了是一种理想状态,因为数据特点的不同,我们构造出来的二叉查找树可能会退化成链表结构,比如每次都一直插入左子树或一直插入右子树,这个时候查找时间复杂度就是 O(n), 这是不可接受的。那怎么办呢?

问题出在了构建二叉查找树的时候,在插入和删除的时候会影响二叉查找树的形态,所以我们需要控制动态构建的这个二叉查找树,让他保持平衡,以便于更加接近一颗完全二叉查找树,这样就是一个比较好的状态。为了解决二叉查找树的时间复杂度退化问题,保持平衡,我们可以动态的维护一颗平衡二叉查找树(AVL),这个就是我们想要的。

平衡二叉查找树是一颗任何节点的左右子树高度相差不超过1的二叉查找树,可见AVL是对二叉查找树的进一步限制。但是AVL的实现还是比较复杂的,在维护平衡时,涉及到了多种情况和各种旋转操作。

AVL的实现有很多,如红黑树,伸展树,AA树,treap 树等,这些树的工程实现普遍都很复杂,每一个都值得写一篇长长的总结。

对树来说,其实落在工程中的实现是像红黑树这种性能表现比较好的数据结构,通过一步步的分析,我们也知道了为什么不在工程中直接使用一般化的二叉查找树,而是使用AVL。无非是分析各种情况下,给出的这个数据结构和算法的设计,在最好和最坏的情况下有多大差别,我们是否能容忍最坏情况的发生,这个需要根据工程中的实际场景来权衡。数据结构和算法没有哪一个是完美的,只有最合适的,我们要分析实际场景。

  • 一个特殊化的完全二叉树:堆

完全二叉树是一个平衡树,所以高度最大是 O(logn),所以堆具有这个性质;此外,堆规定了每个节点的值必须大于等于(小于等于)其子树中每个节点的值;可见,堆是一颗受限的完全二叉树。

  • B+Tree:由二叉查找树演变而来的多叉索引树

有这样一个实际应用,我们需要一个数据库,要将数据持久化存储在磁盘上,并且支持: 1. 根据某个值快速查找数据 2. 根据某个区间值来查找数据 3. 希望能够按顺序输出某个区间内的数据,升序和降序

我们可以构建这样一颗平衡二叉查找树,叶子节点存储数据,非叶子节点只存储索引,这样索引和数据就可以分开存储了;但是二叉查找树可以快速查找数据,但是无法进行区间查找和按序输出;我们可以将叶子节点用双向链表串联起来,这样就可以支持区间查找和顺序输出了。

存在另外一个问题,因为数据库有存储大量数据的需求,比如可能是上亿条数据,如果把索引节点都放在内存中是不现实的,我们希望索引不占用太多的内存空间,那只能把一部分索引放在内存,一部分放在磁盘,等需要的时候再把磁盘的索引载入内存(时间换空间的思路,牺牲了一定的时间)磁盘的访问速度远低于内存访问速度;我们知道平衡二叉查找树的查找是 O(logn),采用这种方式后,会带来一个问题,就是需要多次访问磁盘来加载索引文件,会严重降低查找的速度,一次磁盘IO需要10ms左右,对于一个有上亿数据量的数据查找,需要大概20次左右的磁盘IO,就是200ms,所以真的是很慢;

那如何解决平衡二叉查找树带来的磁盘IO过多的问题呢?答案是降低树的高度,树的高度减少了,磁盘需要IO的次数就对应减少了,同时还能更好的利用内存的局部性原理,增加树的度可以减少树的高度,比如每个树的度最大是1000时;这样就形成了一颗m叉的平衡查找树。这正是B+树的核心,也是众多存储引擎使用的数据结构。

当然,B+树在存储引擎的实现要复杂很多,要考虑的点也有很多,专门的探讨放在后续的总结中。

通过分析各种类型的树及其操作,很清楚的知道了他们的由来、特性、适用的场景以及它能解决的问题,而这个也是我们学习数据结构和算法最核心的地方;在理解了这些的基础上,然后用代码去实现出来并不断优化工程代码。

上一页二分思想下一页算法思想总结

最后更新于4年前

这有帮助吗?

堆适合使用数组来存储。更加详细的分析参考:

堆和堆排序