文章目录
- [一、 数据结构中的「堆」:核心概念与核心特性](#一、 数据结构中的「堆」:核心概念与核心特性)
-
- 一、堆的核心定义
-
- [1. 堆的两种类型(按堆序性划分)](#1. 堆的两种类型(按堆序性划分))
- 二、堆的存储结构(数组是最优选择)
- 三、堆的核心操作(基础+高频)
-
- [1. 插入元素(上浮操作)](#1. 插入元素(上浮操作))
- [2. 删除堆顶元素(下沉操作)](#2. 删除堆顶元素(下沉操作))
- [3. 建堆(从无序数组生成堆)](#3. 建堆(从无序数组生成堆))
- 四、堆的关键特性总结
- 五、堆的典型应用场景
- 示例:大顶堆的简单C语言表示
- 总结
- [二、 数据结构中的「树」:核心概念与核心特性](#二、 数据结构中的「树」:核心概念与核心特性)
-
- 一、树的核心定义(形式化+通俗解释)
-
- [1. 形式化定义](#1. 形式化定义)
- [2. 通俗理解](#2. 通俗理解)
- 二、树的核心术语(必懂)
- 三、树的基本特性(核心规律)
- 四、树的常见分类(按结构/用途划分)
- 五、树的存储方式
-
- [1. 链式存储(通用,适合所有树)](#1. 链式存储(通用,适合所有树))
- [2. 顺序存储(仅适合完全二叉树/满二叉树)](#2. 顺序存储(仅适合完全二叉树/满二叉树))
- 六、树的典型应用场景
- 总结
一、 数据结构中的「堆」:核心概念与核心特性
数据结构中的「堆(Heap)」是一种基于完全二叉树的特殊树形结构,核心特征是「结构上满足完全二叉树,顺序上满足堆序性」------需注意和编程中「内存堆区」(动态内存分配区域)区分,二者无直接关联。
堆是实现「优先队列」「堆排序」的核心数据结构,广泛用于TopK、任务调度、中位数维护等场景。
一、堆的核心定义
堆是满足以下两个条件的完全二叉树:
- 结构条件:必须是「完全二叉树」(层序遍历填满,最后一层节点从左到右连续排列,无空缺);
- 顺序条件(堆序性):任意父节点与子节点满足固定的大小关系(分两种类型)。
1. 堆的两种类型(按堆序性划分)
| 堆类型 | 核心规则 | 示例(数组存储) | 可视化二叉树结构 |
|---|---|---|---|
| 大顶堆(最大堆) | 任意父节点的值 ≥ 所有子节点的值 | [9,7,8,5,3,4] |
根节点9,左子7、右子8;7的子5/3,8的子4 |
| 小顶堆(最小堆) | 任意父节点的值 ≤ 所有子节点的值 | [2,5,3,7,6,4] |
根节点2,左子5、右子3;5的子7/6,3的子4 |
⚠️ 关键:堆仅约束「父-子」关系,不约束兄弟节点的大小关系(比如大顶堆中7和8是兄弟,8>7仍符合规则)。
二、堆的存储结构(数组是最优选择)
堆本质是完全二叉树,而完全二叉树的节点可通过「数组下标」直接映射,无需额外存储左右子节点指针,空间效率极高。
数组与完全二叉树的下标映射规则
假设堆用数组arr存储,数组下标从0开始,对任意节点i:
- 父节点下标:
parent(i) = (i - 1) // 2(整数除法,如i=5→parent=2); - 左孩子下标:
left(i) = 2 * i + 1(如i=1→left=3); - 右孩子下标:
right(i) = 2 * i + 2(如i=1→right=4)。
示例:大顶堆的数组映射
数组[9,7,8,5,3,4]对应的完全二叉树:
9 (i=0)
/ \
7(i=1) 8(i=2)
/ \ /
5(3) 3(4) 4(5)
验证规则:
- 节点8(i=2)的父节点:(2-1)//2=0 → 9(正确);
- 节点7(i=1)的左孩子:21+1=3 → 5(正确),右孩子:21+2=4 →3(正确)。
三、堆的核心操作(基础+高频)
堆的操作围绕「维护堆序性」展开,核心是「上浮(sift up)」和「下沉(sift down)」两个基础动作:
- 上浮:节点值违反堆序性(如小顶堆中子节点<父节点),向上交换至正确位置;
- 下沉:节点值违反堆序性(如大顶堆中父节点<子节点),向下交换至正确位置。
1. 插入元素(上浮操作)
核心思路:先把新元素放到数组末尾(完全二叉树最后一个位置),再从下往上上浮,修复堆序性。
步骤(以大顶堆为例):
- 将新元素
val添加到数组末尾; - 计算新元素下标
i = 数组长度-1; - 循环:
- 计算父节点下标
p = (i-1)//2; - 若
arr[i] > arr[p](大顶堆规则),交换arr[i]和arr[p]; - 更新
i = p,直到i=0(根节点)或arr[i] ≤ arr[p]。
- 计算父节点下标
2. 删除堆顶元素(下沉操作)
堆仅支持删除「根节点(堆顶)」,这是堆的核心特性(优先队列取最值)。
核心思路:用数组最后一个元素替换堆顶,再从上往下下沉,修复堆序性。
步骤(以大顶堆为例):
- 保存堆顶值(
arr[0]),用于返回; - 将数组最后一个元素移到堆顶(
arr[0] = arr[len-1]),删除最后一个元素; - 初始化当前节点下标
i=0; - 循环:
- 计算左孩子
l=2i+1、右孩子r=2i+2; - 找到
arr[i]、arr[l]、arr[r]中的最大值下标max_idx; - 若
max_idx != i(当前节点不是最大值),交换arr[i]和arr[max_idx]; - 更新
i = max_idx,直到i无孩子或arr[i] ≥ 子节点。
- 计算左孩子
3. 建堆(从无序数组生成堆)
核心思路:从最后一个非叶子节点开始,逐个向下下沉,避免从根节点开始的低效操作。
步骤(以大顶堆为例):
- 计算最后一个非叶子节点下标:
last_non_leaf = (len(arr)-1 -1)//2 = len(arr)//2 -1; - 从
last_non_leaf到0逆序遍历每个节点i; - 对每个
i执行「下沉操作」,最终生成大顶堆。
四、堆的关键特性总结
| 特性 | 说明 |
|---|---|
| 结构特性 | 完全二叉树,可用数组高效存储,无需指针 |
| 顺序特性 | 仅约束父-子关系,兄弟节点无顺序要求 |
| 最值特性 | 大顶堆顶是最大值,小顶堆顶是最小值,取最值时间复杂度O(1) |
| 操作复杂度 | 插入/删除堆顶:O(log n)(树高度为log₂n);建堆:O(n)(而非O(n log n)) |
| 稳定性 | 堆排序是不稳定排序(交换操作会破坏相等元素的相对位置) |
五、堆的典型应用场景
- 堆排序:利用大顶堆/小顶堆实现O(n log n)的排序,原地排序(空间复杂度O(1));
- 优先队列:任务调度(高优先级任务先执行)、事件驱动系统(按时间戳处理事件);
- TopK问题:找数组中前K大/小的元素(用小顶堆存前K大元素,效率高于排序);
- 中位数维护:用大顶堆存左半部分(≤中位数),小顶堆存右半部分(≥中位数),快速取中位数;
- 多路归并:合并多个有序数组(如外部排序、数据库查询结果合并)。
示例:大顶堆的简单C语言表示
c
#include <stdio.h>
#define MAX_HEAP_SIZE 100
int heap[MAX_HEAP_SIZE];
int heap_size = 0;
// 交换两个数
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 上浮操作(插入时用)
void sift_up(int i) {
while (i > 0) {
int p = (i - 1) / 2; // 父节点下标
if (heap[i] > heap[p]) { // 大顶堆:子>父则交换
swap(&heap[i], &heap[p]);
i = p;
} else {
break;
}
}
}
// 插入元素
void insert(int val) {
if (heap_size >= MAX_HEAP_SIZE) return;
heap[heap_size++] = val;
sift_up(heap_size - 1);
}
int main() {
// 插入元素构建大顶堆
insert(5);
insert(9);
insert(7);
insert(3);
insert(8);
// 输出堆数组:[9,8,7,3,5](大顶堆)
for (int i = 0; i < heap_size; i++) {
printf("%d ", heap[i]);
}
return 0;
}
总结
堆的核心是「完全二叉树结构 + 堆序性规则」,其价值在于快速获取最值 (O(1))和高效更新堆(O(log n)),是处理"最值优先"类问题的最优数据结构。理解堆的关键是掌握「上浮/下沉」操作,以及数组与完全二叉树的下标映射关系。
二、 数据结构中的「树」:核心概念与核心特性
树(Tree)是非线性层次化数据结构,核心特征是「一对多」的节点关系(区别于数组、链表等「一对一」的线性结构),通过「节点」和「边」组织数据,能高效表达层级、嵌套、分类关系(如文件系统、族谱、数据库索引)。
树是数据结构的核心基础,二叉树、堆、红黑树、B+树等都是其衍生结构,广泛应用于工程场景。
一、树的核心定义(形式化+通俗解释)
1. 形式化定义
树是 n(n≥0) 个节点的有限集合:
- 若
n=0:称为「空树」; - 若
n>0:
① 有且仅有一个根节点(Root) (无父节点的唯一节点);
② 其余n-1个节点分为m(m≥0)个互不相交的子集,每个子集本身是一棵独立的树(称为「子树」),且子树之间无连接。
2. 通俗理解
把树倒过来看(根在上,叶子在下):
- 根节点是"树干顶端",唯一且无上级;
- 子节点是"树枝分叉",每个节点的子节点数量称为「度」;
- 叶子节点是"树枝末端的叶子",无下级节点;
- 边是"连接树枝的部分",节点数 = 边数 + 1(核心特性)。
示例:简单树的结构
根节点(A)
/ | \
B C D ------ A的子节点(B/C/D互为兄弟节点)
/ \ |
E F G ------ B的子节点(E/F是叶子节点);C的子节点G
二、树的核心术语(必懂)
| 术语 | 定义(结合上面示例) |
|---|---|
| 根节点 | 树的起始节点,无父节点(示例中A) |
| 叶子节点 | 度为0的节点(无子女),示例中E、F、G、D |
| 父节点/子节点 | 若节点X直接指向Y,则X是Y的父节点,Y是X的子节点(A是B的父,B是A的子) |
| 兄弟节点 | 同一父节点的子节点(B、C、D互为兄弟;E、F互为兄弟) |
| 节点的度 | 节点拥有的子节点个数(A的度=3,B的度=2,E的度=0) |
| 树的度 | 树中所有节点的度的最大值(示例树的度=3) |
| 层(深度) | 根节点为第1层(或第0层,两种计数方式均常见),子节点为下一层(A=1层,B/C/D=2层,E/F/G=3层) |
| 高度 | 从节点到叶子节点的最长路径的层数(A的高度=3,B的高度=2,E的高度=1) |
| 路径 | 从一个节点到另一个节点的节点序列(A→B→E 是一条路径,长度=2,路径长度=边数) |
| 祖先/后代 | 路径上的所有上层节点是祖先,下层节点是后代(A是E的祖先,E是A的后代) |
| 森林 | m(m≥0) 棵互不相交的树的集合(若删除根节点A,B、C、D各自的子树构成森林) |
三、树的基本特性(核心规律)
- 节点与边的关系:任意树中,节点数 = 边数 + 1(每条边连接一个父节点和子节点,根节点无父边);
- 度的总和:所有节点的度之和 = 边数 = 节点数 - 1(每个边对应一个子节点,度的总和即子节点总数);
- 空树特性:空树无节点、无边,是树的特殊情况;
- 层级限制 :若树的高度为h(根为1层),则节点数最少为h(每层1个节点),最多为 2 h − 1 2^h - 1 2h−1(满二叉树的情况)。
四、树的常见分类(按结构/用途划分)
树的衍生结构均基于「二叉树」扩展,以下是工程中最常用的类型:
| 树类型 | 核心定义 | 关键特性 | 典型应用 |
|---|---|---|---|
| 二叉树 | 每个节点最多有2个子节点(左子树、右子树),子树有左右顺序(不能互换) | 任意二叉树,第k层最多有 2 k − 1 2^{k-1} 2k−1 个节点;高度h的二叉树最多有 2 h − 1 2^h -1 2h−1 个节点 | 所有二叉衍生树的基础 |
| 满二叉树 | 二叉树中,所有层的节点都被填满(除叶子层外,每个节点都有2个子节点) | 叶子节点全部在最后一层;节点数= 2 h − 1 2^h -1 2h−1(h为高度) | 理论研究、完美二叉树场景 |
| 完全二叉树 | 二叉树中,最后一层节点从左到右连续排列(前h-1层是满二叉树) | 可通过数组高效存储(无需指针);堆的底层结构 | 堆、优先队列 |
| 二叉搜索树(BST) | 二叉树 + 有序性:左子树所有节点值 < 根节点值 < 右子树所有节点值 | 查找/插入/删除平均时间复杂度O(log n),最坏O(n)(退化为链表) | 简单有序数据查询 |
| 平衡二叉树(AVL) | BST + 平衡性:任意节点的左右子树高度差 ≤ 1 | 严格平衡,查询效率稳定O(log n);插入/删除需旋转调整 | 高频查询、低修改的场景 |
| 红黑树 | BST + 红黑规则(节点标红/黑,确保最长路径≤2×最短路径) | 近似平衡,插入/删除调整成本低;查询略慢于AVL,但修改更高效 | Java TreeMap、Linux内核调度 |
| B树/B+树 | 多路平衡查找树(节点可存多个关键字,有多个子树) | B+树所有数据在叶子节点,叶子节点链式连接;B树数据分布在所有节点 | 数据库索引(MySQL)、文件系统 |
| 堆 | 完全二叉树 + 堆序性(大顶堆:父≥子;小顶堆:父≤子) | 取最值O(1),插入/删除O(log n);不稳定排序 | 堆排序、TopK、优先队列 |
| Trie树(字典树) | 按字符串字符分层的树,每个节点存一个字符,路径对应字符串 | 字符串前缀匹配效率极高(O(字符串长度)) | 拼音输入法、路由表、关键词检索 |
| 哈夫曼树 | 带权路径长度最短的二叉树(权值大的节点离根近) | 构建哈夫曼编码,无冗余压缩 | 数据压缩(JPG/PNG)、编码 |
五、树的存储方式
树的存储需表达「节点-子节点」的关系,常见两种方式:
1. 链式存储(通用,适合所有树)
每个节点包含「数据域 + 子节点指针域」:
-
二叉树节点示例(C语言):
ctypedef struct TreeNode { int data; // 数据域 struct TreeNode *left; // 左子节点指针 struct TreeNode *right; // 右子节点指针 } TreeNode; -
多叉树节点示例(用指针数组存子节点):
c#define MAX_CHILD 3 // 树的度为3 typedef struct MultiTreeNode { int data; struct MultiTreeNode *children[MAX_CHILD]; // 子节点指针数组 } MultiTreeNode;
2. 顺序存储(仅适合完全二叉树/满二叉树)
利用数组下标映射节点关系(无需指针,空间高效),规则:
- 根节点存于下标0(或1);
- 若节点存于下标
i(下标从0开始):- 左子节点:
2*i + 1; - 右子节点:
2*i + 2; - 父节点:
(i - 1) / 2(整数除法)。
- 左子节点:
- 示例:完全二叉树
[A,B,C,D,E]存于数组arr[0]=A, arr[1]=B, arr[2]=C, arr[3]=D, arr[4]=E。
六、树的典型应用场景
- 文件系统:根目录→子目录→文件,是树的经典层级结构;
- 数据库索引:MySQL用B+树作为索引结构,快速定位数据行;
- 编译器/解释器:语法分析生成「抽象语法树(AST)」,解析代码结构;
- 路由算法:网络路由表用Trie树存储IP地址,快速匹配路由规则;
- 人工智能:决策树用于分类/回归(如判断用户是否流失);
- 数据压缩:哈夫曼树生成哈夫曼编码,压缩文本/图片数据;
- 任务调度:堆(完全二叉树)实现优先队列,高优先级任务先执行。
总结
树的核心是「层次化、一对多」的节点关系,其价值在于:
- 相比线性结构,能更高效地表达层级/嵌套数据(如文件系统);
- 衍生结构(BST、红黑树、B+树)解决了"有序数据的快速查询/插入/删除"问题;
- 堆、Trie树等专用树结构,针对性解决"最值优先""字符串匹配"等场景。
理解树的关键是掌握「节点、度、层、高度」等核心术语,以及二叉树的基本特性------所有复杂树结构均是二叉树的扩展或优化。