p25(15 分钟)、p26(16 分钟)
树是什么
前面学的数组、链表、栈、队列都是线性结构——有逻辑起点和终点,每个元素有前驱后继。树不是:它是非线性结构,是层级结构。
选数据结构的四个考虑
① 要存什么② 操作成本(尤其想压低「最坏」操作成本) 例:频繁查找就把集合按有序形式存成数组,以便二分③ 内存消耗④ 易于实现(这一条未必是最佳取舍标准)定义与应用
定义:一组称为节点的实体相互连接,模拟层级关系。
最顶端节点 = 根。节点含数据(任意类型)+ 指向其他节点的链接/引用。
应用举例:公司组织层级(John CEO → 直接下属 Steve、Rama;Steve 管 Bob、Ella……)。
树要倒过来看才像树,根在顶部向下分支。
节点编号 1~10 只是为了方便指代,不表示任何顺序,根节点叫成 10 号节点也一样。
术语
根节点 最顶端节点,唯一没有父节点子节点/父节点 直接链接关系兄弟节点 有同一个父节点叶节点 没有子节点内部节点 至少有一个子节点祖父/孙子 父的父 / 子的子祖先/后代 能沿链路从 A 走到 B,则 A 是祖先、B 是后代堂兄弟姐妹(表亲)没有相同父节点但有相同祖父节点叔伯 父的兄弟链路是单向的:有 1→2 的链路,能从 1 到 2,不能从 2 回 1;遍历时只能沿一个方向走。
递归性质与边数
树是递归的数据结构:一个特殊节点(根)+ 若干子树,根包含指向所有子树根的链接。
N 个节点的树恰好有 N−1 条边。理由:除根外每个节点恰好有一条入边。
深度 vs 高度
| 定义 | 起点 | |
|---|---|---|
| 深度 | 从根到节点 X 的路径长度 = 路径上的边数 | 根节点深度 = 0 |
| 高度 | 从节点 X 到叶节点的最长路径中的边数 | 叶节点高度 = 0 |
树的高度 = 根节点的高度。
高度和深度是两个不同属性,一个节点的两者可能相同也可能不同,这里很容易混。
树的应用
① 存储天然分层数据(文件系统的文件/文件夹层次)② 整理数据以便快速搜索/插入/删除 —— 二叉查找树,搜索时间复杂度约为对数级③ Trie 存词典,速度极快、效率高,用于动态拼写检查④ 网络路由算法二叉树
基本约定
- 每节点最多 2 个子节点
- 一个节点可能:有两个 / 只有左 / 只有右 / 都没有
- 只有一个子节点时,另一个引用/指针设为 NULL
- 叶节点:左、右引用均为空
二叉树的三种类型
| 类型 | 定义 |
|---|---|
| 严格二叉树(strict / 正规) | 每个节点要么有 2 个子节点、要么 0 个(不能只有 1 个) |
| 完全二叉树 | 除可能的最后一层外所有层都填满,且最后一层节点尽可能靠左(左侧不能有空位) |
| 完美二叉树 | 所有层都被完全填满 |
单节点树也算二叉树;唯一条件就是不能超过两个子节点。
层与每层最大节点数
相同深度的节点处于同一层;根深度 0 → 第 0 层(L0),往下 L1、L2…
第 I 层最多 2^I 个节点。推导:某层有 X 个节点,则下一层最多 2X 个。
L0: 2^0 = 1L1: 2^1 = 2L2: 2^2 = 4L3: 2×4 = 8高度公式
完美二叉树高度 H(层数 H+1)的节点总数:
N = 2^0 + 2^1 + … + 2^H = 2^(H+1) − 1例:层数 4(L0~L3)→ 最大节点数 = 2^4 − 1 = 15。
反解:
N = 2^(H+1) − 1→ H = log₂(N+1) − 1
例:N = 15 → H = log₂16 − 1 = 4 − 1 = 3完全二叉树高度:
H = ⌊log₂ N⌋
例:N = 15 → log₂15 ≈ 3.906891 → 取整数部分 = 3N 个节点的最小高度 = ⌊log₂ N⌋(完全/完美二叉树时达到)。
N 个节点的最大高度 = N − 1(树稀疏到近似链表时)。
为什么树的高度重要
树上的操作开销取决于树的高度:BST 的查找/插入/删除与高度成正比。
时间复杂度写成 O(h)
完全/完美二叉树:h = log₂N → O(log N)(几乎是可能达到的最佳运行时间)最坏(链表状): h = N−1 → O(N)直观对比:即便 N 高达 2^100,log₂N 也只是 100;而 O(N) 时若 N = 2^100,即使使用有史以来最强大的机器,几年都无法完成计算。
所以通常要使二叉树高度尽可能小,也就是保持二叉树「平衡」。
平衡二叉树
定义:对每个节点,其左右子树高度差不超过某个数 K;大多数情况下 K = 1。
差值计算:|h_left − h_right|。
计算中子树为空时,其高度取 −1。
叶节点的左右子树均为空 → h_left = h_right = −1 → 差值为 0满二叉树所有节点 DIF 都为 0删掉一些节点后最大 DIF = 1,仍平衡再删后某节点 DIF = 2(左子树高 1、右子树为空高 −1,|1−(−1)| = 2)→ 不再平衡目的:保持紧凑、高度最小 → 依赖高度的操作成本最低。
高度的两种定义
这里采用的定义:
高度 = 从根到叶的最长路径中的边数
由此:只有一个节点的树高度 = 0 空树高度 = −1另一种定义(不采用)是把最长路径上的节点数量当高度。
那样:最长路径 3 条边 → 高度会是 4 单节点树高度 = 1 空树高度 = 0那种定义看起来非常直观,很多地方都这么写,但这里一律用「边数」定义,所以空树高度是 −1。
二叉树的两种内存存储方式
指针/引用链接动态创建的节点 —— 最常见,节点随机分布在堆里数组 —— 通常用于完全二叉树(后面堆也用它)数组的编号方式:从根开始、自左向右按层编号,从 0 开始(0, 1, 2, 3, 4, 5, 6)。
索引为 I 的节点: 左子节点索引 = 2I + 1 右子节点索引 = 2I + 2例:I = 0 → 左 1、右 2;I = 1 → 左 3、右 4;I = 2 → 左 5、右 6。
这个公式仅对完全二叉树成立。
这是 0-based 的公式。有些教材用 1-based(左 2i、右 2i+1),这里用 0-based。
核心数值速查
N 个节点的边数 N − 1第 I 层最多节点数 2^I完美二叉树节点数 2^(H+1) − 1完美二叉树高度 log₂(N+1) − 1完全二叉树高度 ⌊log₂ N⌋N 个节点的最小高度 ⌊log₂ N⌋N 个节点的最大高度 N − 1数组存储(0-based) 左 2I+1、右 2I+2坑
- 根节点深度 = 0,叶节点高度 = 0,空树高度 = −1。
- 深度从上往下数,高度从下往上数,别混。
- 平衡因子里空子树的高度取 −1,否则叶节点的 DIF 会算错。
- 数组下标公式是 0-based(2I+1 / 2I+2),且只对完全二叉树成立。
- 「完全二叉树」只是三分类之一,另外两个是严格二叉树和完美二叉树;链路单向,不能从子回到父。
请输入编辑凭据,只有站点所有者可以修改文章。