数据结构1-二叉树

二叉树详解

目录

二叉树详解

[一、 什么是二叉树?](#一、 什么是二叉树?)

二叉树的5个核心性质:

[二、 常见二叉树的分类](#二、 常见二叉树的分类)

二叉树分类速查表

[💡 分类逻辑说明](#💡 分类逻辑说明)

[1. 满二叉树 (Full Binary Tree)](#1. 满二叉树 (Full Binary Tree))

[2. 完全二叉树 (Complete Binary Tree)](#2. 完全二叉树 (Complete Binary Tree))

[3. 完美二叉树 (Perfect Binary Tree)](#3. 完美二叉树 (Perfect Binary Tree))

[4. 平衡二叉树 (Balanced Binary Tree)](#4. 平衡二叉树 (Balanced Binary Tree))

[4.1. AVL树​ - 严格平衡](#4.1. AVL树 - 严格平衡)

[4.2. 红黑树​ - 近似平衡](#4.2. 红黑树 - 近似平衡)

[4.3. 对比AVL和红黑树](#4.3. 对比AVL和红黑树)

[5. 二叉搜索树 (Binary Search Tree, BST)](#5. 二叉搜索树 (Binary Search Tree, BST))

[6. 退化二叉树/斜树 (Degenerate/Skewed Tree)](#6. 退化二叉树/斜树 (Degenerate/Skewed Tree))

[三、 特殊二叉树类型](#三、 特殊二叉树类型)

[1. 线索二叉树 (Threaded Binary Tree)](#1. 线索二叉树 (Threaded Binary Tree))

[2. 哈夫曼树 (Huffman Tree)](#2. 哈夫曼树 (Huffman Tree))

[3. 表达式树 (Expression Tree)](#3. 表达式树 (Expression Tree))

[4. 堆 (Heap)](#4. 堆 (Heap))

[1. 插入 (Insert) - 向上调整 (Sift Up)](#1. 插入 (Insert) - 向上调整 (Sift Up))

[2. 删除堆顶 (Pop/Extract-Max) - 向下调整 (Sift Down)](#2. 删除堆顶 (Pop/Extract-Max) - 向下调整 (Sift Down))

[3. 获取极值 (Peek)](#3. 获取极值 (Peek))

[4. 建堆 (Heapify) - 自底向上构建](#4. 建堆 (Heapify) - 自底向上构建)

[✅ 操作总结](#✅ 操作总结)

[四、 二叉树的存储方式](#四、 二叉树的存储方式)

[1. 链式存储](#1. 链式存储)

[2. 数组存储(完全二叉树)](#2. 数组存储(完全二叉树))

[五、 二叉树的遍历方式](#五、 二叉树的遍历方式)

[六、 时间复杂度对比](#六、 时间复杂度对比)

[七、 实际应用](#七、 实际应用)

[八、 常用计算](#八、 常用计算)

[九、 如何选择合适的二叉树?](#九、 如何选择合适的二叉树?)


一、 什么是二叉树?

二叉树 是每个节点最多有两个子节点的树形数据结构。它是一种特殊的树,具有以下特性:

复制代码
基本结构:
         A        ← 根节点
        / \
       B   C      ← 子节点
      / \   \
     D   E   F    ← 叶子节点

二叉树的5个核心性质:

  1. 每个节点最多有两个子节点:左子节点和右子节点

  2. 子树有左右之分:左子树 ≠ 右子树

  3. 第i层最多有 2^(i-1) 个节点

  4. 深度为k的二叉树最多有 2^k - 1 个节点

  5. 叶子节点数 = 度为2的节点数 + 1,(度 ​ 指的是节点拥有的子节点数量)

二、 常见二叉树的分类

二叉树分类速查表

分类维度 类型名称 核心定义 关键特征
结构形态 满二叉树 (Full) 所有层都达到最大节点数 叶子节点全在最底层,节点总数 2h−1(h为高度)
完全二叉树 (Complete) 除最后一层外全满,最后一层靠左填满 数组存储效率高,无空间浪费
平衡二叉树 (Balanced) 左右子树高度差不超过1 如AVL、红黑树,保证操作复杂度为 O(logn)
退化二叉树/斜树 (Degenerate) 每个节点最多只有一个子节点 失去树的结构优势,操作复杂度退化为 O(n)
数据特性 二叉搜索树 (BST) 左子树所有节点 < 根 < 右子树所有节点 支持高效的搜索、插入、删除(平均 O(logn))
堆 (Heap) 根节点是最大/最小值(大顶堆/小顶堆) 常用于优先队列、快速找极值,不保证整体有序
线索二叉树 (Threaded) 利用空指针指向遍历前驱/后继 优化中序遍历,无需递归或栈即可遍历

💡 分类逻辑说明

  1. 形态是骨架 :满二叉树、完全二叉树等描述的是树的"物理形状"。在实际应用中,完全二叉树因其适合用数组紧凑存储(无指针开销),在堆结构中被广泛使用。

  2. 特性是灵魂 :二叉搜索树(BST)和堆描述的是节点数据的"逻辑关系"。BST 维护了有序性,而只关心极值。

  3. 平衡是关键 :普通的BST在插入有序数据时会退化成链表。平衡二叉树 (如AVL树、红黑树)通过自平衡机制,确保树的高度始终可控,是大多数语言标准库(如std::map)的实现基础。

记忆口诀满则全,全未必满;查用BST,极值用堆;防退化,必平衡。

1. 满二叉树 (Full Binary Tree)

定义 :除了叶子节点外,每个节点都有两个 子节点

特点

  • 所有叶子都在同一层

  • 节点总数 = 2^k - 1(k为深度)

  • 是"最丰满"的二叉树

    示例(深度3,7个节点):
    1
    /
    2 3
    / \ /
    4 5 6 7

2. 完全二叉树 (Complete Binary Tree)

定义 :除了最后一层,其他层都是满的,且最后一层的节点尽量靠左排列

特点

  • 可以用数组紧凑存储

  • 堆排序、优先队列的基础

  • 高度 = ⌊log₂n⌋ + 1 (深度为k的二叉树最多有 2^k - 1 个节点

    有效完全二叉树:
    1
    /
    2 3
    / \ /
    4 5 6

    无效完全二叉树(6不在左边):
    1
    /
    2 3
    / /
    4 5 6

3. 完美二叉树 (Perfect Binary Tree)

定义:所有叶子节点都在同一层,且每个非叶子节点都有两个子节点

注意:完美二叉树一定是满二叉树,但满二叉树不一定是完美二叉树(深度可以不同)

4. 平衡二叉树 (Balanced Binary Tree)

定义 :任意节点的左右子树高度差不超过1

常见类型

  • AVL树:严格平衡,|左高-右高| ≤ 1

  • 红黑树:近似平衡,保证最长路径 ≤ 2×最短路径

    平衡二叉树示例:
    5
    /
    3 8
    / \
    2 4 9
    /
    1
    高度差 ≤ 1

AVL树和红黑树都属于BBT(平衡二叉树)。它们是平衡二叉搜索树(Balanced Binary Search Tree)的两种经典实现,都通过特定的平衡机制来保证树的高度为O(log n),从而确保搜索、插入、删除等操作的高效性。

4.1. AVL树​ - 严格平衡

(AVL ​ 是两个人名姓氏的缩写,来源于其发明者 G. M. Adelson-Velsky ​ 和 Evgenii Landis。)

核心特性 :通过旋转操作保持绝对平衡,任意节点的左右子树高度差不超过1。

示例AVL树结构(数值:10, 20, 30, 40, 50, 25):

python 复制代码
       30
      /  \
    20    40
   /  \     \
  10  25    50

平衡特征

  • 节点30:左子树高2(20,10,25),右子树高2(40,50),高度差=0

  • 节点20:左子树高1(10),右子树高1(25),高度差=0

  • 节点40:左子树高0,右子树高1(50),高度差=1

4.2. 红黑树​ - 近似平衡

核心特性 :通过红黑着色规则保持近似平衡,最长路径不超过最短路径的2倍。

示例红黑树结构(数值:10, 20, 30, 40, 50, 60, 70):

python 复制代码
       40(B)
      /    \
   20(R)   60(B)
   /  \    /  \
 10(B)30(B)50(R)70(R)

着色规则

  1. 每个节点非红即黑

  2. 根节点为黑色B

  3. 红色节点的子节点必须为黑色

  4. 从任一节点到其所有叶子节点的路径包含相同数量的黑色节点

  5. 叶子节点(NIL)视为黑色

4.3. 对比AVL和红黑树
特性 AVL树 红黑树
平衡严格度 严格(高度差≤1) 宽松(最长路径≤2×最短路径)
旋转频率 高(插入/删除常需多次旋转) 低(通常最多3次旋转)
适用场景 搜索密集型(如数据库索引) 插入删除频繁(如STL map/set)
时间复杂度 搜索O(log n),插入/删除O(log n) 搜索O(log n),插入/删除O(log n)
实现复杂度 较高 相对较低

定义:对于每个节点:

  • 左子树所有节点的值 < 当前节点的值

  • 右子树所有节点的值 > 当前节点的值

特点

  • 中序遍历得到有序序列

  • 查找、插入、删除:平均O(log n),最坏O(n)

    二叉搜索树示例:
    6
    /
    3 8
    / \
    1 5 9

    2

6. 退化二叉树/斜树 (Degenerate/Skewed Tree)

定义:每个节点都只有一个子节点

特点

  • 退化成链表

  • 时间复杂度退化为O(n)

    左斜树: 右斜树:
    1 1
    /
    2 2
    /
    3 3

三、 特殊二叉树类型

1. 线索二叉树 (Threaded Binary Tree)

  • 利用空指针域指向遍历序列的前驱/后继

  • 加快遍历速度

核心:将原本为 NULL的指针,指向 该节点在 某种遍历顺序下 的前驱或后继,从而在不使用栈或递归的情况下实现快速遍历。

**线索化:**​

将二叉树中的空指针域重新利用:

• 左空指针 →指向前驱

• 右空指针 →指向后继

**标记位:**​

每个指针增加标志位(tag):

tag=0:指向孩子(原树结构)

tag=1:指向线索(前驱/后继)

**遍历方式:**​

通常基于中序遍历(LNR)建立线索,也可前序、后序

2. 哈夫曼树 (Huffman Tree)

  • 带权路径最短的二叉树

权: 就是一个权重值,通常代表某种"重要性"或"代价"。

在哈夫曼树中 :权就是字符出现的频率(或概率)。

例如:字符 A出现 50 次,它的权就是 50。

直观理解:权越大,说明这个节点(字符)越重要、用得越多,我们就希望它离根节点越近(路径越短),这样整体效率才高。

路径长度 就是从根节点 走到该节点 所经过的边数(注意:不是节点数)。

带权路径长度(WPL) ​ = ​ × 路径长度

哈夫曼树 用于数据压缩,带权路径长度(WPL) 越小,意味着高频字符的路径越短,整体编码或查找的平均代价就越低。

哈夫曼树的目标就是构造一棵二叉树,使得这个 WPL 值最小。这就是为什么它被称为"最优二叉树"。

3. 表达式树 (Expression Tree)

  • 叶子节点是操作数

  • 内部节点是运算符

    表达式 (a+b)*(c-d) 的树:
    *
    /
    + -
    / \ /
    a b c d

4. 堆 (Heap)

  • 特殊的完全二叉树

  • 最大堆:父节点 ≥ 子节点

  • 最小堆:父节点 ≤ 子节点

堆(Heap)是一种基于完全二叉树 实现的高效数据结构,它通过父子节点间的大小约束 来快速获取极值,是**优先队列(Priority Queue)**的标准底层实现。

特性 最大堆 (Max Heap) 最小堆 (Min Heap)
结构基础 完全二叉树(可用数组紧凑存储) 完全二叉树(可用数组紧凑存储)
核心规则 父节点值 **≥**​ 任一子节点值 父节点值 **≤**​ 任一子节点值
堆顶极值 根节点是最大值 根节点是最小值
局部有序 只保证"父大于子",不保证整体有序 只保证"父小于子",不保证整体有序

关键操作与性能

操作 逻辑 时间复杂度
**插入 (Insert)**​ 元素放末尾,向上调整 (Sift Up) O(logn)
**删除堆顶 (Pop)**​ 末尾元素换到根,向下调整 (Sift Down) O(logn)
**获取极值 (Peek)**​ 直接返回根节点(数组首元素) O(1)
**建堆 (Heapify)**​ 从最后一个非叶子节点开始向下调整 O(n)

这四种操作是堆(Heap)的核心。

以最常见的最大堆 (父 ≥ 子)为例,配合图示来讲解每个操作的原理、步骤和复杂度。


1. 插入 (Insert) - 向上调整 (Sift Up)

目标:将一个新元素加入堆,并维持堆的性质。

操作步骤

  1. 放末尾 :将新元素插入到数组的末尾(即完全二叉树的最后一个叶子位置)。

  2. 向上调整 (Sift Up)

    • 比较新元素与其父节点。

    • 如果新元素 > 父节点,则交换它们的位置。

    • 重复此过程,直到新元素 ≤ 其父节点,或到达根节点。

图解示例 (向最大堆 [10, 7, 8, 3, 2] 插入 12)

对应的完全二叉树为:

复制代码
        10
       /  \
      7    8
     / \
    3   2

插入新元素 12

按照完全二叉树的规则,新元素必须添加到最后一层的最左空闲位置(即数组末尾)。插入后数组变为 [10, 7, 8, 3, 2, 12],对应的树结构为:

复制代码
        10
       /  \
      7    8
     / \   /
    3   2 12
  • 节点 12 的索引是 5,其父节点索引为 (5-1)//2 = 2,即节点 8。

  • 因此,12 的父节点是 8,而不是 7

正确的向上调整过程

  1. 比较 12 与其父节点 8:12 > 8,交换它们,树变为:

    复制代码
          10
         /  \
        7    12
       / \   /
      3   2 8
  2. 现在 12 的索引是 2,父节点索引为 (2-1)//2 = 0,即根节点 10。比较 12 与 10:12 > 10,交换,得到:

    复制代码
          12
         /  \
        7    10
       / \   /
      3   2 8
  3. 调整结束,最终数组为 [12, 7, 10, 3, 2, 8]

时间复杂度:O(logn),因为调整路径最长等于树的高度。


2. 删除堆顶 (Pop/Extract-Max) - 向下调整 (Sift Down)

目标:移除并返回堆顶元素(最大值/最小值),并重新调整堆。

操作步骤

  1. 记录并移除 :记录堆顶(根节点)的值作为返回值。将堆的最后一个叶子节点的值移到根节点,并删除最后一个叶子。

  2. 向下调整 (Sift Down)

    • 从新的根节点开始,比较它与其左右子节点。

    • 如果它小于任何一个子节点 ,则与较大的那个子节点交换。

    • 重复此过程,直到该节点 ≥ 其所有子节点,或到达叶子。

图解示例 (从最大堆 [12, 10, 8, 3, 7, 2] 删除堆顶 12)

复制代码
初始堆:
        12
       /  \
      10   8
     / \  /
    3   7 2
1. 移除堆顶 12,将最后一个元素 2 移到根:
        2
       /  \
      10   8
     / \  /
    3   7
2. 向下调整:2 与较大的子节点 10 比较,2 < 10,交换:
        10
       /  \
      2    8
     / \  /
    3   7
3. 继续向下:2 与较大的子节点 7 比较,2 < 7,交换:
        10
       /  \
      7    8
     / \  /
    3   2
调整完成。新堆为 [10, 7, 8, 3, 2],返回 12。

时间复杂度:O(logn)。


3. 获取极值 (Peek)

目标:查看堆顶元素而不移除它。

操作:直接返回数组的第一个元素(即根节点)。

复制代码
堆顶:12
数组:[12, 10, 8, 3, 7, 2]
返回 12,堆结构无任何变化。

时间复杂度:O(1)。


4. 建堆 (Heapify) - 自底向上构建

目标:将一个无序的数组(或完全二叉树)在 O(n)时间内调整成一个合法的堆。

操作步骤

  1. 找到起点 :从最后一个非叶子节点 开始(即从索引 n/2 - 1开始)。

  2. 自底向上向下调整 :对从该节点到根节点的每一个节点,执行一次 **向下调整 (Sift Down)**​ 操作。

原理:向下调整操作能保证以当前节点为根的子树满足堆的性质。从下往上做,可以确保调整每个节点时,其子树已经是合法的堆。

图解示例 (将无序数组 [4, 10, 3, 5, 1] 建为最大堆)

复制代码
初始完全二叉树:
        4
       / \
      10  3
     / \
    5   1
1. 最后一个非叶子节点是 10(索引 1)。
2. 对 10 向下调整:10 ≥ 5 且 10 ≥ 1,无需调整。
3. 对上一个节点 4(索引 0)向下调整:
   4 与较大的子节点 10 比较,交换:
        10
       /  \
      4    3
     / \
    5   1
4. 继续对交换后的节点 4(现在在索引 1)向下调整:
   4 与较大的子节点 5 比较,交换:
        10
       /  \
      5    3
     / \
    4   1
建堆完成,得到最大堆 [10, 5, 3, 4, 1]。

时间复杂度 :看似是 O(nlogn),但经过数学推导,实际是 O(n),这使其非常高效。


✅ 操作总结
操作 核心方法 时间复杂度 关键步骤
**插入(Insert)**​ 末尾添加,向上调整 (Sift Up) O(logn) 1. 放末尾 2. 向上比较交换
**删除堆顶(Pop)**​ 首尾交换,向下调整 (Sift Down) O(logn) 1. 首尾互换 2. 末尾移除 3. 新根向下调整
**获取极值(Peek)**​ 直接访问根节点 O(1) 返回 array[0]
**建堆(Heapify)**​ 自底向上向下调整 O(n) n/2-1开始,对每个节点向下调整

记忆口诀插尾向上浮,删顶换尾向下沉;看顶 O(1),自底建堆 O(n)。

数组存储映射(完全二叉树特性)

利用完全二叉树的特性,堆通常用数组实现,父子关系通过下标计算(假设根节点索引为 0):

  • 父节点索引parent(i) = (i - 1) // 2

  • 左孩子索引left_child(i) = 2*i + 1

  • 右孩子索引right_child(i) = 2*i + 2

    数组: [1, 2, 3, 4, 5, 6, 7]
    对应的树:
    1(0)
    /
    2(1) 3(2)
    / \ /
    4(3) 5(4) 6(5) 7(6)

应用场景

  • 优先队列:任务调度(总是处理优先级最高的任务)。

  • Top K 问题:用最小堆维护当前最大的 K 个数。

  • 堆排序:利用最大堆不断取出最大值进行排序。

记忆口诀堆是棵完全树,数组能存储;最大堆爹最大,最小堆爹最小;取极值 O(1),插删 O(log n)。

四、 二叉树的存储方式

1. 链式存储

复制代码
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

2. 数组存储(完全二叉树)

  • 索引从0开始:

    • 父节点i → 左子节点:2i+1

    • 父节点i → 右子节点:2i+2

    • 子节点i → 父节点:(i-1)/2

      数组: [1, 2, 3, 4, 5, 6, 7]
      对应的树:
      1(0)
      /
      2(1) 3(2)
      / \ /
      4(3) 5(4) 6(5) 7(6)

五、 二叉树的遍历方式4种

遍历方式 顺序 应用场景
前序遍历 根 → 左 → 右 复制树、求表达式
中序遍历 左 → 根 → 右 BST得到有序序列
后序遍历 左 → 右 → 根 删除树、计算高度
层序遍历 按层从上到下 求宽度、序列化
复制代码
// 前序遍历示例
void preorder(TreeNode* root) {
    if (!root) return;
    cout << root->val << " ";  // 访问根
    preorder(root->left);      // 遍历左子树
    preorder(root->right);     // 遍历右子树
}

六、 时间复杂度对比

类型 查找平均 查找最坏 插入平均 插入最坏 空间 应用
普通二叉树 O(log n) O(n) O(log n) O(n) O(n) 通用
平衡BST O(log n) O(log n) O(log n) O(log n) O(n) 数据库索引
完全二叉树 O(log n) O(log n) O(log n) O(log n) O(n) 堆排序
斜树 O(n) O(n) O(n) O(n) O(n) 应避免

七、 实际应用

  1. 数据库索引:B树、B+树(多路平衡树)

  2. 文件系统:目录结构

  3. 编译器:语法分析树

  4. 数据压缩:哈夫曼编码

  5. 游戏AI:决策树

  6. 路由算法:最短路径树

  7. 排序算法:堆排序

八、 常用计算

  1. 二叉树节点数:n = n₀ + n₁ + n₂

  2. 边与节点关系:边数 = n - 1

  3. 叶子节点计算:n₀ = n₂ + 1

  4. 高度h的二叉树:最少h个节点,最多2^h-1个节点

九、 如何选择合适的二叉树?

  1. 需要快速查找​ → 平衡BST(AVL/红黑树)

  2. 需要优先队列​ → 堆

  3. 需要数据压缩​ → 哈夫曼树

  4. 需要表达式求值​ → 表达式树

  5. 需要紧凑存储​ → 完全二叉树

相关推荐
爱睡懒觉的焦糖玛奇朵6 小时前
【工业级落地算法之人员摔倒检测算法详解】
人工智能·python·深度学习·神经网络·算法·yolo·目标检测
小辉同志6 小时前
78. 子集
算法·leetcode·深度优先
Book思议-6 小时前
【数据结构】二叉树入门全解:从定义、性质到经典真题
数据结构·算法·二叉树
Mem0rin6 小时前
[Java/数据结构]线性表之链表
java·数据结构·链表
人大博士的交易之路7 小时前
数据结构算法——python数据结构
开发语言·数据结构·python
stolentime7 小时前
通信题:洛谷P15942 [JOI Final 2026] 赌场 / Casino题解
c++·算法·洛谷·joi·通信题
初生牛犊不怕苦7 小时前
与AI一起学习《C专家编程》:数组与指针
c语言·学习·算法
Kk.08028 小时前
数据结构|排序算法(二) 冒泡排序
数据结构·算法·排序算法
沛沛rh458 小时前
深入并发编程:从 C++ 到 Rust 的学习笔记
c++·笔记·学习·算法·rust
Kk.08029 小时前
数据结构|排序算法(二) 希尔排序
数据结构·算法·排序算法