一、红黑树
以前我们接触二叉查找树的时候,在理想的情况下,数据操作的时间复杂度为O( log (N) ),但在极端的情况下,二叉查找树容易退化成一个链表,时间复杂度变成了O(N),很不稳定。
后来我们引入了平衡二叉树,并规定左右子树高度差不能超过1,在最差情况下时间的复杂度为O( 2log(n) ),但是平衡二叉树在新增和删除操作的时候需要频繁的进行“旋转”操作,例如在删除操作的时,必须检查从被删除节点开始到根节点路径上的所有节点的情况,这就时期性能大打折扣。
我们说平衡二叉树为了维持左右子树高度差不超过1的平衡,进行了大量的“旋转”操作,那么有没有一种树,既能维持平衡,又能降低频繁的“旋转”操作呢?那就是红黑树。
红黑树通过牺牲严格的平衡来换取新增和删除时的少量旋转操作,其整体性能优于平衡二叉树。
一颗典型的红黑树如下所示:
红黑树的性质如下:
二、B树
1、B树的引出
以前我们学过的二叉排序树、平衡二叉树在进行动态查找的时候主要是在内存中实现的,而且数据量小。二叉搜索树的搜索效率和树高成正比关系,通过减少二叉搜索树的高度,可以提高搜索效率。平衡二叉树可以减少树高,但是仍然不够彻底,因为每个节点只含有一个关键字,树高仍然为O(logn),能否继续压缩树高,使其更加扁平化呢?
如果一个节点不限于存储一个关键字,就可以包含多个关键字和多个子树,既保持二叉
搜索树的特性,又具有平衡性,这样的搜索树称为多路平衡搜索树,如图所示。
平衡二叉搜索树比普通的二叉搜索树高低,而多路平衡搜索树的树高更低,更加扁平化,查询的效率更高。多路平衡搜索树主要用于大规模数据的分级存储搜索,将内存的一个“大节点”和外存的“大容量”结合起来,提高搜索效率。
B树的出现是为了弥合不同的存储级别之间的访问速度上的巨大差异,实现高效的 I/O。平衡二叉树的查找效率是非常高的,并可以通过降低树的深度来提高查找的效率。但是当数据量非常大,树的存储的元素数量是有限的,这样会导致二叉查找树结构由于树的深度过大而造成磁盘I/O读写过于频繁,进而导致查询效率低下。另外数据量过大会导致内存空间不够容纳平衡二叉树所有结点的情况。B树是解决这个问题的很好的结构。
B树的节点通常较大,可以存储多个关键字和指针,适合用于访问磁盘或其他外部存储介质上的数据。B树的节点大小通常与磁盘块大小相匹配,减少了I/O操作的次数,提高了数据的读写效率。
2、B树的定义
(1)什么是B树的阶
B树中所有节点中孩子节点的最大值称为B树的阶。阶数通常用m表示,从查找效率考虑,要求m>=3,例如下列为5阶B树
(2)B树的定义
一颗m阶B树或者是一颗空树,或者是满足下列要求的m叉树:
【1】树中每个节点至多有m个孩子节点,即至多有m-1个关键字
【2】除了根节点之外,其它系欸但至少有m/2个孩子节点,即至少有[m/2]-1个关键字
【3】若根节点不是叶子节点,则根节点至少有两个孩子节点
【4】节点的数据结构:
-- n为该节点中的关键字个数
-- 除了根节点外,对于其它所有节点[m/2] - 1 <= n <= m-1
-- Ki(1<=i<=n)为该节点的关键字且满足Ki < Ki+1
-- Pi(0<=i<=n)为该节点的孩子节点指针,且对Pi节点上的关键字K, Ki <= K < Ki+1
【5】所有叶子节点在同一层上
B树的C语言数据结构描述如下:
// B树的节点结构
typedef struct BTreeNode {
int *keys; // 关键字数组
struct BTreeNode **child; // 子节点指针数组
bool isLeaf; // 是否叶子节点
int numKeys; // 关键字数量
} BTreeNode;
// B树结构
typedef struct BTree {
BTreeNode *root; // 根节点指针
int t; // B树的度数
} BTree;
在这个数据结构描述中,BTreeNode表示B树的节点,包含关键字数组keys、子节点指针数组child、一个表示是否为叶子节点的布尔变量isLeaf以及关键字数量numKeys。BTree表示整个B树,包含根节点指针root以及B树的度数t。
3、B树的查找
查找步骤:
- 从根节点开始,按照节点上的关键字进行搜索。
- 如果关键字等于目标值,则找到了。
- 如果关键字大于目标值,则进入节点的左子树继续搜索。
- 如果关键字小于目标值,则进入节点的右子树继续搜索。
- 如果节点不包含目标值并且没有子节点,则搜索失败。
示例:
使用{6、3、8、1、5}构造一个度数为2的B树,描述查找的思想、步骤并画出该B树图形:
【1】 先将{6、3、8、1、5}依次插入到空的B树中:
6
/ \
3 8
/ \
1 5
【2】 根据查找思想,开始查找目标值。
- 查找目标值为5:
- 从根节点开始,根节点为6,比5大,进入左子树。
- 左子树的根节点为3,比5小,进入右子树。
- 右子树的根节点为5,与目标值相等,找到了。
- 查找目标值为9:
- 从根节点开始,根节点为6,比9小,进入右子树。
- 右子树的根节点为8,比9小,继续进入右子树。
- 右子树没有节点,搜索失败。
- 查找目标值为2:
- 从根节点开始,根节点为6,比2大,进入左子树。
- 左子树的根节点为3,比2大,继续进入左子树。
- 左子树的左子树没有节点,搜索失败。
#include <stdio.h>
#include <stdlib.h>
#define M 2 // B树的度数
#define MAX_KEYS (2 * M - 1) // 节点的最大关键字数
// B树的节点结构
typedef struct Node {
int num_keys; // 关键字数目
int keys[MAX_KEYS]; // 关键字数组