B树和B+树是平衡树的一种,在数据库、文件系统等大规模存储系统中广泛应用,主要用于实现高效的查找、插入和删除操作。它们都具有自平衡的特性,能够在最坏情况下提供对数时间复杂度的操作。
1. B树(B-Tree)
定义
B树是一种自平衡的多路查找树,它将数据存储在节点中并按顺序排列。B树的每个节点可以包含多个子节点和多个元素,因此它在存储大量数据时非常高效。
性质
- 每个节点可以有多个子节点:B树的每个节点不仅有一个数据项,而且可以有多个子节点,允许节点有多个键值。
- 节点的键值按顺序排列:节点内部的键值是有序的,从左到右逐渐增大。
- 根节点至少有两个子节点(除非是空树),其他节点至少有 ⌈m/2⌉ 个子节点,其中 m 是树的阶数。
- 节点的键数限制:一个节点最多包含 m - 1 个键,最少包含 ⌈m/2⌉ - 1 个键。
- 查找效率:B树的查找操作从根节点开始,依次向下查找子树,时间复杂度为 O(log n),其中 n 为树中的元素个数。
- 插入与删除操作:插入和删除操作保持树的平衡,当节点满时,会分裂节点;当节点键值过少时,会进行合并操作。
- 树的高度相对较低:由于每个节点包含多个键,B树的高度通常比较低,因此查找和操作效率较高。
适用场景
- 数据库索引:B树适用于大规模存储数据,广泛应用于数据库和文件系统中的索引结构。
- 磁盘存储:由于B树具有较高的存储密度和较少的树高,它非常适合磁盘存储系统,减少了磁盘I/O操作的次数。
cpp
#define SUB_M 3
typedef struct _btree_node {
int *keys; // int keys[2 * SUB_M - 1]; 5
struct _btree_node **childrens; //struct _btree_node *childrens[2 * SUB_M - 1]; 6
int num;
int leaf;
}btree_node;
typedef struct _btree {
struct _btree_node *root;
}
2. B+树(B+ Tree)
定义
B+树是B树的变种,B+树的所有值都存在叶子节点,并且叶子节点通过链表连接。它与B树的不同之处在于,B+树的内部节点只存储键值,不存储实际数据,所有数据只存在叶子节点。
性质
- 所有值都存在叶子节点:B+树的非叶子节点只包含键值,实际的数据值只存储在叶子节点中。
- 叶子节点通过链表连接:所有叶子节点组成一个链表,这使得B+树的范围查询非常高效。可以通过叶子节点之间的链表快速访问范围内的数据。
- 内部节点只存储键值:B+树的非叶子节点只存储键值信息,并不存储数据,简化了节点的存储结构。
- 与B树相比,B+树的查询效率较高:因为数据存储在叶子节点并且叶子节点形成链表,所以在范围查询时,B+树可以通过链表进行顺序访问,提高了范围查询的效率。
- 插入与删除:B+树的插入和删除操作与B树类似,都是通过节点分裂和合并来保持树的平衡。由于内部节点不存储数据,B+树的插入和删除操作通常较为高效。
- 树的高度:B+树的高度通常和B树相同,因为它们的阶数和高度结构是一样的。
适用场景
- 数据库索引:B+树是现代数据库管理系统中最常用的索引结构,因为它在范围查询和顺序扫描方面更具优势。常见的数据库系统如 MySQL、Oracle 等都使用 B+ 树作为索引结构。
- 文件系统:文件系统中的目录结构、文件的查找、顺序读取等操作通常采用 B+ 树来组织数据。
- 磁盘和SSD存储:由于数据和指针的分离,B+树在磁盘或SSD上能够更有效地减少I/O操作,适合大规模的数据存储和访问。
3.B-树的删除
先合并在删除
先借位在删除
cpp
#define SUB_M 3
typedef struct _btree_node {
int *keys; // int keys[2 * SUB_M - 1]; 5
struct _btree_node **childrens; //struct _btree_node *childrens[2 * SUB_M - 1]; 6
int num;
int leaf;
}btree_node;
typedef struct _btree {
struct _btree_node *root;
}btree;
// 创建结点
btree_node *btree_create_node(int leaf) {
btree_node *node = (btree_node*)calloc(1, sizeof(btree_node));
if (node == NULL) return NULL;
node->leaf = leaf;
node->keys = calloc(2 * SUB_M - 1, sizeof(int));
node->childrens = (btree_node**)calloc(2 * SUB_M, sizeof(btree_node*));
node->num = 0;
return node;
}
// 删除节点
void btree_destroy_node(btree_node *node) {
free(node->childrens);
free(node->keys);
free(node);
}
// 分裂
void btree_split_child(btree *T, btree_node *x, int idx) {
btree_node *y = x->childrens[idx];
btree_node *z = btree_create_node(y->leaf);
//// 对z操作
z->num = SUB_M - 1;
int i = 0;
for (i = 0; i < SUB_M-1; i++) {
z->keys[i] = y->keys[SUB_M + i];
}
if (y->leaf == 0) { //inner内结点
for (i = 0; i < SUB_M; i++) {
z->childrens[i] = y->childrens[SUB_M + i];
}
}
////对y操作
y->num = SUB_M - 1;
for (i = x->num; i >= idx + 1; i--) {
x->childrens[i + 1] = x->childrens[i];
}
x->childrens[idx + 1] = z;
for (i = x->num-1; i >= idx; i--) {
x->keys[i+1] = x->keys[i];
}
x->keys[idx] = y->keys[SUB_M - 1];
x->num += 1;
}
//插入操作
void btree_insert(btree *T, int key) {
btree_node *r = T->root;
if (r->num == 2 * SUB_M - 1) {
btree_node *node = btree_create_node(0);
T->root = node;
node->childrens[0] = r;
btree_split_child(T, node, 0);
}
}
//合并操作
void btree_merge(btree *T, btree_node *x, int idx) {
btree_node *left = x->childrens[idx];
btree_node *right = x->childrens[idx+1];
int i = 0;
/////data merge
left->keys[left->num] = x->keys[idx];
for (i = 0;i < right->num; i++) {
left->keys[SUB_M + i] = right->keys[i];
}
if (!left->leaf) {
for (i = 0;i < SUB_M; i++) {
left->childrens[SUB_M + i] = right->childrens[i];
}
}
left->num += right->num + 1; // 合并键数应该是左节点数量 + 右节点数量 + 1(来自父节点的键)
//destroy right
btree_destroy_node(right);
//node
for (i = idx+1; i < x->num; i++) {
x->keys[i-1] = x->keys[i];
x->childrens[i] = x->childrens[i+1];
}
x->childrens[i+1] = NULL;
x->num -= 1;
if (x->num == 0) {
T->root = left;
btree_destroy_node(x);
}
}
参考链接:0voice · GitHub