红黑树,B树,二叉树之间的不同
二叉树
二叉树是一种树状结构,每个节点最多只有两个子节点:左子节点和右子节点。
结构
A/ \B C/ \ \D E F
这里:
- A是根节点
- B和C是A的子节点
- D和E是B的子节点
- F是C的子节点
基本术语
- 节点(Node):树的每个元素,比如A、B、C
- 根节点(Root):树的最顶端节点,比如以上示例的A
- 叶子节点(Leaf):没有子节点的节点(D、E、F)
- 子节点(Child):某节点的直接下级节点(B是A的子节点,D是B的子节点)
- 父节点(Parent):某节点的上级节点(D的父节点是B,B的父节点是A)
- 深度(Depth):节点到根的距离(层数)
- 高度(Height):节点到叶子节点的最长路径
深度(Depth) | 高度(Height) | |
---|---|---|
定义 | 节点到根的距离(层数) | 节点到叶子的最长路径(层数) |
例子 | 根深度为0,叶子深度为最大 | 叶子高度为0,树根高度最大,反映从节点到叶子路径 |
深度和高度不同,不要搞错了。
类型
类型 | 描述 | 举例 |
---|---|---|
普通二叉树 | 任意节点最多两个子节点 | 上面示意的树 |
满二叉树 | 所有叶子节点都在同一层,且每个非叶子节点有两个子节点 | 每层的节点数都满 |
完全二叉树 | 除最后一层外,每层节点都满,最后一层节点尽可能集中在左侧 | 完全二叉堆 |
平衡二叉树 | 任意节点左右子树高度差不超过1 | AVL树、红黑树 |
遍历方式
- 前序遍历(Pre-order): 根 -> 左子树 -> 右子树
- 中序遍历(In-order): 左子树 -> 根 -> 右子树
- 后序遍历(Post-order): 左子树 -> 右子树 -> 根
- 层序遍历(Level-order): 按层从上到下、从左到右
示例:
假设树如下:
A/ \B C/ \ \D E F
- 前序:A -> B -> D -> E -> C -> F
- 中序:D -> B -> E -> A -> C -> F
- 后序:D -> E -> B -> F -> C -> A
- 层序:A, B, C, D, E, F
性质
-
节点数关系:
在一棵二叉树中,节点总数为:∇=叶子节点数+非叶子节点数
-
深度与高度:
- 树的深度(层数)越多,树越高
- 平衡的二叉树操作通常为了减少树的高度,以优化性能
二叉搜索树
对于树中的每个节点:
- 左子树上所有节点的值都小于这个节点的值
- 右子树上所有节点的值都大于这个节点的值
这样,整棵树就按照“二叉搜索树的性质”排序了。
实例
假设插入数字:10, 5, 15, 3, 7
结构大致是:
10/ \5 15/ \3 7
- 每个节点左边都比它小,右边都比它大
- 可以快速实现搜索、插入、删除
作用
- 快速查找:通过中序遍历,得到一个有序序列
- 基础结构:很多复杂的树(如AVL、红黑树)都是在BST基础上改进的
- 应用广泛:数据库索引、字典等
平衡二叉树
平衡二叉树是一类特殊的二叉搜索树,其任何节点的左右子树的高度差(平衡因子)都不超过1。这样保证树的高度始终保持在 logn 的级别。
使用平衡二叉树的原因
-
普通BST在极端情况下可能退化成链表:
- 比如按顺序插入有序数据,会形成一条长链,查找变为线性时间 O(n)。
-
平衡树维持树的高度最小化,保证查找、插入、删除操作高效。
常见的平衡二叉树
-
AVL树(Adelson-Velsky and Landis Tree)
- 平衡因子(Balance factor):每个节点左子树高度减右子树高度,要求为-1、0、1。
- 每次插入或删除后,可能需要“旋转”操作来修复平衡。
-
红黑树
- 性质:每个节点是红或黑,满足特定规则,使树始终大致平衡。
- 特点:插入、删除后通过“变色”和“旋转”保持平衡,常用于Java的TreeMap、C++的map。
实例
以AVL树为例:如何保持平衡
-
插入/删除节点后:
- 从插入位置向上回溯,检测每个节点的平衡因子
- 如果某个节点的平衡因子超出范围(>1 或 <-1),就需要“旋转”调整
-
旋转类型:
-
左旋(Single Left Rotation)
-
右旋(Single Right Rotation)
-
左右旋(Left-Right Rotation)
-
右左旋(Right-Left Rotation)
这些旋转操作帮助重新平衡树,保持每个节点的平衡因子在允许范围内。
旋转
单旋转
- LL旋转(左左不平衡 -> 右旋)
场景:
插入新节点后,左子树的左子节点变得过高,导致树左侧“倾斜”。
不平衡前 旋转后A B/ / \B —— 右旋 —— C A/ C
- 将A进行右旋转 将节点B作为新的根节点,A成为B的右子节点,C(原B的左子节点)作为A的左子节点。
- RR旋转(右右不平衡 -> 左旋)
场景:
插入新节点后,右子树的右子节点变得过高。
不平衡前 旋转后A B\ / \B —— 右旋 —— A C\ \C D
- 将A左旋转 成为左节点
双旋转
- LR旋转(左-右不平衡)
场景:
左子树的右子树高,需先左旋左子树,再右旋根节点。
不平衡前 旋转后A / B —— 先左旋 B 在右旋A —— B \ / \ C C A
总结
特点 | 说明 |
---|---|
每个节点的平衡因子(左右子树高度差) | ≤ 1 \leq 1 ≤1(即-1, 0, 1) |
保持树的高度在 log n \log n logn 级别 | 确保操作时间为 $ O(\log n) $ |
通过旋转调整平衡 | 在插入、删除操作后自动修复 |
旋转子树时其他不变,当子树旋转结束后,谁是子树根节点,全树的根节点就连接谁。
红黑树
红黑树是一类自平衡的二叉搜索树,它通过“颜色标记”以及一系列规则,确保树的高度大致平衡,从而在插入、删除和查找操作中实现 O(logn) 的时间复杂度。
节点属性
每个节点有:
- 一个值(数据)
- 一个颜色(红或黑)
- 在红黑树的定义中,黑色节点的数量(黑高)决定了路径的长度,而红色节点只是“装饰”
- 红节点不增加黑色节点的数量,只是“点缀”,用于调节树结构。
- 黑节点:像“楼层数”,决定了路径的基本高度。
- 红节点:像“楼层之间的装饰”,不会加高,但帮助控制设计。
- 指向左右子节点的指针
- 指向父节点的指针(有的实现会有)
结构
(8B)/ \(4R) (12R)/ \ / \(2B) (6B) (10B) (14B)
- R代表红色,B代表黑色
关键属性
- 节点是红色或黑色
- 根节点是黑色
- 保持树的“起点”黑色,有助于维护路径上黑色节点的平衡。
- 这样可以避免频繁出现连续的红色起点,也简化性质的维护。
- 所有叶子(NIL节点,即空子节点)都是黑色
- 方便统一处理空子节点,简化算法设计。
- 通过将叶子定义为黑色,确保路径中的黑色节点计数一致。
- 红色节点的子节点必须是黑色(不能连续两个红节点)
- 连续的红色节点会导致路径中的黑色节点数变少,从而使树高度偏高,平衡性差
- 从任何节点到叶子节点的所有路径都包含相同数目的黑色节点(黑色平衡)
作用与优势
- 保持树的平衡:通过颜色规则避免树变得太高(退化为链表)。
- 保证操作效率:插入、删除、查找都在 O(logn) 范围内。
插入操作
插入新节点类似BST:
- 新节点总是插入为红色(为了不是破坏黑色平衡)
- 然后修复颜色和旋转,保持红黑性质
插入修复(修复红色连续性)
可能出现:
- 红节点的父节点也是红色(违反性质 4)
修复方法:
- 情况1:叔叔节点为红色
A/ \B C/ \D E
-
D的父节点是 B。
-
B的兄弟节点(父节点的兄弟)就是 C。
- 将父节点和叔叔节点变为黑色
- 将祖父节点变为红色
- 继续向上修正
-
情况2:叔叔节点为黑色或不存在(空节点)
- 进行左旋或右旋调整,以恢复性质,往往以“旋转+颜色调整”完成
状况 | 处理方法 |
---|---|
父节点是红色,叔叔节点是红色 | 变色 + 重新向上修复 |
父节点是红色,叔叔节点是黑色(或不存在) | 旋转 + 变色(形成左右旋转的调整) |
例:
插入节点7
- 作为红色节点插入
- 如果父节点是黑色,无需调整
- 如果父节点是红色(出现连续红色),进行旋转和颜色调整
删除操作
- 类似BST,但需要调整来恢复红黑性质
- 可能涉及“向下传递”的颜色和旋转调整
核心思想
- 红黑树通过限制“连续红节点“和”黑色路径长度一致“,保证树的高度在”对数级“
- 调整主要依靠颜色变换和旋转
总结
特性 | 描述 |
---|---|
节点颜色 | 每个节点是红或黑 |
根节点 | 必须是黑色 |
叶子节点(NIL) | 全部是黑色,作为叶节点,用来简化算法 |
红色节点的子节点 | 必须是黑色(不能连续两个红色节点) |
所有路径的黑色节点数相同 | 从根到任意叶子路径,黑色节点数一致(黑高平衡) |
如果出现连续红节点会出现的问题:
- 形成连续的红色链(如:连续三个红色节点),
- 破坏黑色节点平衡的性质(因为黑色节点数变少了),
- 使得树的高度可能增长,变得不平衡。
红黑树的设计宗旨是限制红色链的长度:
- 规则“红色节点的子节点必须是黑色”,实际上限制了连续红色的长度最多为1。
- 这样,路径上的红色节点最多只有一层,避免了“红色链”无限长,从而保证树的平衡。
B树
- B树是一棵阶数为 m 的多路平衡搜索树。
- 每个节点最多有 m 个子节点(即 m-1 个关键字)。
基本性质
- 每个内部节点(非叶子节点):
- 含有 k (m/2 - 1 ≤ k ≤ m-1) 个关键字
- 有 k+1 个子节点
- 所有叶子节点在同一层(树的高度平衡)。
- 每个节点(除了根节点),关键字数都在允许范围内。
根节点:- 可以只有1个关键字(或更少,视具体定义而定),但一般至少有一个关键字,除非树为空。
具体阶数
- 阶(阶数)为 m
- 每个节点(除了叶子)至少有 ⌊m/2⌋ 个关键字(除了根)
- 每个节点最多有 m-1 个关键字
结构特点
特性 | 描述 |
---|---|
多路 | 每个内部节点可以有多个子节点(非二叉树)。 |
平衡 | 所有叶子节点在同一层,保证查找、插入、删除的平均和最坏时间都 O(log n)。 |
关键字存储 | 节点内存放有序的关键字,用于导航搜索。 |
节省空间 | 由于多个关键字在一节点,减少树的高度,从而减少磁盘操作。 |
操作
- 1. 查找
- 从根节点开始,比较目标值与当前节点的关键字。
- 根据比较结果,沿对应子节点递归查找。
- 直到找到目标或到达叶子。
- 2. 插入
- 先找到插入位置(类似查找),在叶子节点插入。
- 如果叶子已满(达到最大关键字数),则分裂:
- 将中间关键字上升到父节点
- 分裂成两个节点
- 如果父节点也满,则递归向上分裂。
- 3. 删除
- 找到目标关键字所在节点:
- 如果是叶子,直接删除。
- 如果是内部节点,用前驱或后继节点替换,然后再删除叶子中的值。
- 如果删除后节点关键字数少于最小(合规性),需要借兄弟节点或合并节点。
- 找到目标关键字所在节点:
与B+树的区别
- B树:所有节点(内部和叶子)都存关键字。
- B+树:只有叶子存关键字,内部节点存索引,支持更高的查询效率。
实例
假设 m=4(阶为4):
- 每个节点最多有 3 个关键字和 4 个子节点。
- 每个非根节点至少有 2 个关键字(即⌊4/2⌋)。
简单的B树结构:
[10, 20]/ | \[5, 7] [12, 15] [22, 25]
节点类型 | 关键字 | 子节点说明 | 叶子或非叶子 |
---|---|---|---|
根节点 | [10, 20] | 3个子节点: - 小于10 - 10到20之间 - 大于20 | 非叶子 |
左子节点 | [5, 7] | 无子节点(叶子) (如果它是叶子) | 叶子 |
中左子节点 | [12, 15] | 无子节点(叶子) | 叶子 |
右子节点 | [22, 25] | 无子节点(叶子) | 叶子 |
- 如果这是一个完整的B树,这些叶子可以在同一层,存放实际数据(例如:具体的元素值)。
- 叶子节点中的关键字存放实际数据,或者指向存放数据的存储位置。
节点类型 | 存放内容 | 作用 |
---|---|---|
内部节点(非叶子) | 索引/导航用的关键字 | 引导搜索,决定下一步到哪个子节点 |
叶子节点 | 存放实际数据或数据指针 | 具体的数据存储点;支持范围查询和顺序扫描 |
- 内部节点:
- [10, 20],用来引导:<10,10到20,>20
- 叶子节点:
- [5, 7],[12, 15] ,[22, 25],存放实际数据元素。