B树
B树(B-tree)是一种自平衡的多路搜索树,它被广泛用于文件系统、数据库索引和其他需要快速查找、插入和删除操作的大数据结构中。B树在节点上可以包含多个键和多个子节点,这使得它在磁盘存储和数据操作方面非常高效。
B树的特性
- 每个节点可以包含多个键和多个子节点。
- 所有叶子节点位于相同的深度,保持树的平衡。
- 每个节点至少包含
ceil(m/2) - 1
个键和ceil(m/2)
个子节点,最大包含m - 1
个键和m
个子节点。 - B树的深度较小,可以减少磁盘I/O操作,提高查找速度。
- 所有键都是有序的,使得B树支持快速查找、插入和删除操作。
B树的操作
B树提供了基本的操作,包括查找、插入和删除操作。下面是这些操作的C语言实现。
定义节点结构
首先,我们定义B树的节点结构。节点包含多个键的数组 keys
,多个子节点的数组 children
,以及一个表示当前键数目的字段 numKeys
。
#define MAX_KEYS 4 // 最大键数目
typedef struct BTreeNode {
int keys[MAX_KEYS]; // 键数组
struct BTreeNode *children[MAX_KEYS + 1]; // 子节点数组
int numKeys; // 当前键数目
int isLeaf; // 是否是叶子节点
} BTreeNode;
在上面的代码中,我们定义了B树节点的最大键数目为4,因此一个节点最多可以包含 4 - 1 = 3
个键和 4
个子节点。
查找操作
查找操作在B树中查找特定值。查找过程是递归的,通过比较键的值和目标值来选择正确的子节点。
BTreeNode* search(BTreeNode* node, int value) {
int i = 0;
// 查找键的正确位置
while (i < node->numKeys && value > node->keys[i]) {
i++;
}
// 如果找到了目标键
if (i < node->numKeys && value == node->keys[i]) {
return node;
}
// 如果是叶子节点
if (node->isLeaf) {
return NULL;
}
// 递归查找子节点
return search(node->children[i], value);
}
查找操作递归地遍历树,根据键的值选择正确的子节点进行查找。
插入操作
插入操作在B树中插入一个新的值。如果当前节点已满,则需要进行分裂操作。
// 分裂满的节点
void splitChild(BTreeNode *parent, int index, BTreeNode *child) {
// 创建新的节点
BTreeNode *newNode = (BTreeNode *)malloc(sizeof(BTreeNode));
newNode->isLeaf = child->isLeaf;
newNode->numKeys = MAX_KEYS / 2;
// 复制后一半的键和子节点
for (int j = 0; j < MAX_KEYS / 2; j++) {
newNode->keys[j] = child->keys[j + MAX_KEYS / 2];
}
if (!child->isLeaf) {
for (int j = 0; j <= MAX_KEYS / 2; j++) {
newNode->children[j] = child->children[j + MAX_KEYS / 2];
}
}
// 更新当前节点和子节点的键数目
child->numKeys = MAX_KEYS / 2 - 1;
// 插入新节点到父节点
for (int j = parent->numKeys; j >= index + 1; j--) {
parent->children[j + 1] = parent->children[j];
}
parent->children[index + 1] = newNode;
for (int j = parent->numKeys - 1; j >= index; j--) {
parent->keys[j + 1] = parent->keys[j];
}
parent->keys[index] = child->keys[MAX_KEYS / 2 - 1];
parent->numKeys++;
}
// 插入新值
void insertNonFull(BTreeNode *node, int value) {
int i = node->numKeys - 1;
// 如果是叶子节点
if (node->isLeaf) {
// 找到新值插入的位置
while (i >= 0 && value < node->keys[i]) {
node->keys[i + 1] = node->keys[i];
i--;
}
node->keys[i + 1] = value;
node->numKeys++;
} else {
// 找到合适的子节点
while (i >= 0 && value < node->keys[i]) {
i--;
}
i++;
// 如果子节点已满
if (node->children[i]->numKeys == MAX_KEYS - 1) {
splitChild(node, i, node->children[i]);
// 根据新插入的值决定选择哪个子节点
if (value > node->keys[i]) {
i++;
}
}
insertNonFull(node->children[i], value);
}
}
// 插入操作
void insert(BTreeNode **root, int value) {
BTreeNode *r = *root;
// 如果根节点已满,需要分裂
if (r->numKeys == MAX_KEYS - 1) {
BTreeNode *newRoot = (BTreeNode *)malloc(sizeof(BTreeNode));
newRoot->isLeaf = 0;
newRoot->numKeys = 0;
newRoot->children[0] = r;
splitChild(newRoot, 0, r);
insertNonFull(newRoot, value);
*root = newRoot;
} else {
insertNonFull(r, value);
}
}
插入操作首先检查根节点是否已满,如果已满,则进行分裂操作并将根节点更新为新的根节点。然后,递归地在子节点中插入新值。
删除操作
删除操作在B树中删除特定值,并确保节点满足B树的平衡特性。
// 找到最大值节点
BTreeNode* maxKeyNode(BTreeNode *node) {
while (!node->isLeaf) {
node = node->children[node->numKeys];
}
return node;
}
// 删除操作
void delete(BTreeNode *node, int value) {
int i = 0;
while (i < node->numKeys && value > node->keys[i]) {
i++;
}
if (i < node->numKeys && value == node->keys[i]) {
// 如果找到要删除的键
if (node->isLeaf) {
// 如果是叶子节点,直接删除
for (int j = i; j < node->numKeys - 1; j++) {
node->keys[j] = node->keys[j + 1];
}
node->numKeys--;
} else {
// 非叶子节点
BTreeNode *leftChild = node->children[i];
BTreeNode *rightChild = node->children[i + 1];
if (leftChild->numKeys >= (MAX_KEYS + 1) / 2) {
// 左子节点有足够的键
BTreeNode *predecessor = maxKeyNode(leftChild);
node->keys[i] = predecessor->keys[predecessor->numKeys - 1];
delete(leftChild, predecessor->keys[predecessor->numKeys - 1]);
} else if (rightChild->numKeys >= (MAX_KEYS + 1) / 2) {
// 右子节点有足够的键
BTreeNode *successor = minKeyNode(rightChild);
node->keys[i] = successor->keys[0];
delete(rightChild, successor->keys[0]);
} else {
// 合并左右子节点
mergeChildren(node, i);
delete(node->children[i], value);
}
}
} else {
// 如果要删除的键在子节点中
if (node->isLeaf) {
return;
}
int flag = (i == node->numKeys);
if (node->children[i]->numKeys < (MAX_KEYS + 1) / 2) {
fill(node, i);
}
if (flag && i > node->numKeys) {
delete(node->children[i - 1], value);
} else {
delete(node->children[i], value);
}
}
}
删除操作通过递归的方式删除指定的值。如果要删除的键在叶子节点中,直接删除该键;否则,在子节点中查找并删除。如果子节点的键数不足,需要进行调整操作以保持树的平衡。
总结
B树是一种自平衡的多路搜索树,它适用于需要快速查找、插入和删除操作的大数据结构。B树在数据库索引和文件系统中非常常用,因为它具有较小的深度,可以有效地减少磁盘I/O操作,提高数据操作的效率。