一、树(Tree):分层有序的非线性结构
1.1 树的核心定义与基本术语
树是 n(n≥0) 个结点的有限集合,核心特征是分层、无环、仅有一个根节点:
- 空树:
n=0的特殊情况; - 非空树规则:
- 有且仅有一个 "根结点"(最顶层结点);
- 其余结点可分为
m个互不相交的子集合(子树),每个子树仍是树。
关键术语
| 术语 | 定义 |
|---|---|
| 结点的度 | 结点拥有的子树个数 |
| 叶结点(终端结点) | 度为 0 的结点(无子女) |
| 分支结点(非终端结点) | 度不为 0 的结点 |
| 树的度 | 树中所有结点的度的最大值 |
| 树的深度 / 高度 | 从根开始分层,根为第 1 层,叶子所在的最大层数为树的深度 |
树的存储方式
- 顺序结构:基于数组存储(如完全二叉树的数组表示);
- 链式结构:基于指针存储(如二叉树的链式结点),灵活性更高。
1.2 二叉树(Binary Tree):树的核心子集
二叉树是树中应用最广的结构,定义为:n 个结点的有限集合,要么为空,要么由根结点 + 左子树 + 右子树 组成(左右子树互不相交且有序)。
二叉树核心特点
- 每个结点最多有 2 棵子树(度≤2);
- 左 / 右子树有严格顺序,不可颠倒(即使只有 1 棵子树,也要区分左右);
- 空树、单结点树均属于二叉树。
特殊二叉树
| 类型 | 定义 | 核心特征 |
|---|---|---|
| 斜树 | 所有结点仅有左子树(左斜树)或仅有右子树(右斜树) | 退化为单链表,查找效率 O (n) |
| 满二叉树 | 所有分支结点都有左右子树,且叶子结点在同一层 | 深度为 k 时,结点总数 = 2ᵏ-1 |
| 完全二叉树 | 按层序编号后,每个结点的编号与同深度满二叉树的编号完全一致 | 叶子结点仅出现在最后两层,效率接近满二叉树 |
二叉树的核心特性(必背)
- 第
i层最多有2^(i-1)个结点(i≥1); - 深度为
k的二叉树,至多有2ᵏ - 1个结点(k≥1); - 任意二叉树中,叶子结点数
n₀= 度为 2 的结点数n₂ + 1(n₀ = n₂ + 1); - 有
n个结点的完全二叉树,深度为⌊log₂n⌋ + 1(向下取整)。
1.3 二叉树的遍历方式
遍历是访问二叉树所有结点的核心操作,分为深度遍历 和广度(层序)遍历:
1.3.1 广度遍历(层序遍历)
- 规则:从上到下、从左到右按层访问结点;
- 实现:借助队列(先进先出),根结点入队 → 出队访问 → 左右子结点入队 → 循环至队空;
- 应用:按层打印、求树的宽度、找某一层的结点。
1.3.2 深度遍历(递归 / 栈实现)
| 遍历方式 | 规则 | 示例(根 A,左 B,右 C) |
|---|---|---|
| 前序遍历 | 根 → 左 → 右 | A → B → C |
| 中序遍历 | 左 → 根 → 右 | B → A → C |
| 后序遍历 | 左 → 右 → 根 | B → C → A |
1.4 树的核心应用场景
- 二叉搜索树(BST):有序存储,支持 O (logn) 查找 / 插入 / 删除;
- 平衡二叉树(AVL / 红黑树):解决 BST 退化为链表的问题,用于 Map/Set 底层;
- 堆(完全二叉树):优先队列、TopK 问题、排序(堆排序);
- 多叉树:文件系统目录、XML/JSON 解析、数据库索引。
二、哈希(Hash):极速存取的映射技术
哈希(散列)是一种 "键 - 值" 映射技术,核心是通过哈希函数将数据的 "键(key)" 转换为存储位置,实现 O (1) 级别的存取效率。
2.1 哈希的核心定义
- 存储位置 =
f(key):f:哈希函数(散列函数),负责将 key 转换为数组下标;key:待存储 / 查询的数据;- 存储位置:数组中存储该数据的地址(下标)。
- 核心载体:通常基于顺序表(数组) 实现(需支持随机访问)。
2.2 哈希函数的设计要点
一个优秀的哈希函数需满足两个核心条件:
- 计算简单:快速得到存储位置,避免额外性能开销;
- 地址分布均匀:尽量减少 "哈希冲突",提高空间利用率。
常见哈希函数设计方法
| 方法 | 原理 | 适用场景 |
|---|---|---|
| 直接定值法 | f(key) = a*key + b(a、b 为常数) |
key 分布连续且范围小(如学号、员工编号) |
| 平方取中法 | 将 key 平方后,取中间几位作为下标 | key 分布随机(如字符串、不规则数字) |
| 折叠法 | 将 key 拆分为等长段,叠加后取结果作为下标 | key 位数多(如身份证号、手机号) |
| 求余法 | f(key) = key % size(size 为数组长度) |
通用场景(最常用),建议 size 取质数 |
2.3 哈希冲突:不可避免的问题
冲突定义
f(key1) = f(key2) 但 key1 ≠ key2:不同数据通过哈希函数计算出相同的存储位置。
冲突解决方法
| 方法 | 原理 | 优缺点 |
|---|---|---|
| 线性探测 | 冲突后依次尝试 +1、+2、+3... 直到找到空位置 |
实现简单,易产生 "堆积"(连续冲突) |
| 二次探测 | 冲突后尝试 ±1²、±2²、±4²... 直到找到空位置 |
减少堆积,仍有局部冲突 |
| 随机探测 | 冲突后尝试 +rand()%size 随机位置 |
分布更均匀,需保证遍历所有位置 |
| 链地址法 | 数组每个位置存储一个链表,冲突数据挂在链表上 | 无堆积,效率稳定(HashMap 底层方案) |
2.4 哈希的 ADT(抽象数据类型)定义
c
运行
typedef int DATATYPE;
// 哈希表结构(数组实现)
typedef struct
{
DATATYPE* head; // 哈希表数组指针
int tlen; // 哈希表总长度
} HS_TABLE;
// 核心操作函数
HS_TABLE* CreateHsTable(int len); // 创建哈希表
int insertHsTable(HS_TABLE*hs,DATATYPE*data); // 插入数据
int SearchHsTable(HS_TABLE*hs,DATATYPE*data); // 查询数据
int DestroyHsTable(HS_TABLE*hs); // 销毁哈希表
2.5 哈希的核心应用场景
- 缓存系统(如 Redis):通过 key 极速查找 value;
- 去重 / 计数(如统计字符出现次数);
- 数据库索引:哈希索引(对比 B + 树索引);
- 编程语言容器(如 Java HashMap、Python dict)。
三、树 vs 哈希:核心区别与选型建议
| 维度 | 树(以二叉搜索树为例) | 哈希(链地址法) |
|---|---|---|
| 时间复杂度 | 查找 / 插入 O (logn)(平衡树) | 理想 O (1),冲突时 O (n) |
| 有序性 | 天然有序(中序遍历可排序) | 无序(需额外排序) |
| 空间利用率 | 无浪费(链式存储) | 需预留空间(避免冲突) |
| 适用场景 | 有序查询、范围查找(如找 [10,20] 的数) | 单点极速查询、键值映射 |
| 实现复杂度 | 较高(需处理平衡、遍历) | 较低(核心是哈希函数 + 冲突处理) |
选型建议
- 需有序 / 范围查询:选树(如红黑树、B + 树);
- 需极速单点查询:选哈希(如 HashMap);
- 数据量小、简单场景:哈希更易实现;
- 数据量大、需稳定性能:平衡树更可靠。
四、总结
- 树:以二叉树为核心,通过分层有序存储实现 O (logn) 的查找效率,适合有序、层级化的场景,是解决 "有序遍历、范围查询" 的核心工具;
- 哈希:通过 "键 - 位置" 映射实现 O (1) 极速存取,适合 "单点查询、键值映射" 场景,核心是哈希函数设计和冲突处理;
- 两者互补:工程中常结合使用(如 Redis 的有序集合 = 哈希 + 跳表)。