B+树原理详解及C语言实现

news/2025/2/11 5:28:54/

目录

B+树的原理

B+树的操作过程(图形化演示)

B+树的应用场景

B树与B+树的对比

C语言实现及应用实例

文件结构

总结


B+树的原理

B+树是B树的一种变体,广泛应用于数据库和文件系统中。其原理和特点如下:

  • 数据结构:B+树是一种自平衡的树形数据结构,所有实际数据(键值对)都保存在叶子节点中,内部节点仅存储索引键值,用于引导查找过程。

  • 特点

    • 叶子节点存储所有数据,内部节点仅作为索引使用。
    • 所有叶子节点通过一个链表相互连接,方便进行范围查询或顺序访问。
    • 树的高度平衡,所有叶子节点都处于同一层,保证树的高度稳定,进而保证查询的效率。
  • 优势

    • 提供高效的范围查询,因为叶子节点通过链表连接,可以快速访问连续的数据。
    • 内部节点只需要存储键值,减少了内存使用。
    • 更好的缓存利用,因为所有数据都存储在叶子节点中。

B+树的操作过程(图形化演示)

假设有一个阶为3的B+树,插入操作如下:

初始状态

复制

       [10, 20]/        \[5]        [15] <-> [25, 30]

插入12

  1. 找到插入位置(叶子节点 [15])。

  2. 分裂叶子节点 [15],生成新节点 [15, 12],并调整父节点。

复制

       [10, 20]/        \[5]        [12, 15] <-> [25, 30]

插入22

  1. 找到插入位置(叶子节点 [25, 30])。

  2. 分裂叶子节点 [25, 30],生成新节点 [25, 30, 22],并调整父节点。

复制

       [10, 20, 25]/        |       \[5]      [12, 15]   [22, 25, 30]

B+树的应用场景

  • 数据库:B+树广泛用于数据库索引中,尤其是对范围查询性能有高要求的场景。其有序链表特性使得范围查询变得更加高效,这对于数据库中的诸如BETWEEN、ORDER BY等操作非常有利。如MySQL的InnoDB存储引擎,适合需要高效磁盘访问的场景。
  • 文件存储:B+树也适用于文件系统的实现,能够减少磁盘IO次数,提高文件查找和检索的效率。如Linux的ext3/ext4文件系统。
  • 缓存:B+树的结构使其适用于需要频繁访问和更新数据的缓存系统,能够提供稳定的性能。

B树与B+树的对比

B树B+树
数据结构每个节点包含多个键值和子指针,键值有序排列内部节点仅存储索引键值,叶子节点存储实际数据,叶子节点通过链表连接
查找性能查找、插入、删除操作的时间复杂度为O(log n)提供高效的范围查询,查找性能与B树相当
范围查询范围查询性能可能不如B+树范围查询性能高效,因为叶子节点通过链表连接
磁盘IO适用于磁盘存储,减少磁盘访问次数磁盘IO次数少,因为所有数据都集中在叶子节点,且叶子节点通过链表连接
内存使用内部节点存储数据和索引,可能占用较多内存内部节点仅存储索引,减少了内存使用
平衡性所有叶子节点位于同一层级,保持树的平衡同样保持树的平衡,所有叶子节点处于同一层
易用性实现相对复杂,尤其是在节点的分裂和合并操作中实现相对复杂,特别是在管理叶子节点链表时,但范围查询性能优越
范围查询效率较低效率较高
插入/删除复杂相对简单
叶子节点链接不链接叶子节点按链表顺序链接

C语言实现及应用实例

文件结构

  • btree.h: 头文件,包含数据结构和函数声明。
  • btree.c: 实现文件,包含B+树的各种操作。
  • main.c: 示例应用,展示如何使用B+树。

btree.h

#ifndef BTREE_H
#define BTREE_H#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>#define MAX_KEYS 3  // 每个节点的最大关键字数,可以根据需要调整typedef int KeyType;
typedef int ValueType;typedef struct BPlusTreeNode {int key_count;KeyType keys[MAX_KEYS + 1];  // keys[0]到keys[key_count-1]存储实际关键字,keys[key_count]为指向子节点的指针struct BPlusTreeNode *children[MAX_KEYS + 1];ValueType *values;bool leaf;
} BPlusTreeNode;typedef struct BPlusTree {BPlusTreeNode *root;
} BPlusTree;BPlusTree* create_tree();
BPlusTreeNode* create_node(bool leaf);
void insert(BPlusTree *tree, KeyType key, ValueType value);
BPlusTreeNode* search(BPlusTree *tree, KeyType key, int *index);
void traverse(BPlusTreeNode *node, int depth);
void free_tree(BPlusTree *tree);#endif // BTREE_H

btree.c

#include "btree.h"
#include <string.h>BPlusTree* create_tree() {BPlusTree *tree = (BPlusTree*)malloc(sizeof(BPlusTree));tree->root = create_node(true);return tree;
}BPlusTreeNode* create_node(bool leaf) {BPlusTreeNode *node = (BPlusTreeNode*)malloc(sizeof(BPlusTreeNode));node->key_count = 0;node->leaf = leaf;if (leaf) {node->values = (ValueType*)malloc(sizeof(ValueType) * (MAX_KEYS + 1));memset(node->values, 0, sizeof(ValueType) * (MAX_KEYS + 1));} else {node->values = NULL;}memset(node->keys, 0, sizeof(KeyType) * (MAX_KEYS + 1));memset(node->children, NULL, sizeof(BPlusTreeNode*) * (MAX_KEYS + 1));return node;
}void insert(BPlusTree *tree, KeyType key, ValueType value) {BPlusTreeNode *root = tree->root;if (root->key_count == MAX_KEYS) {BPlusTreeNode *new_root = create_node(false);new_root->children[0] = root;new_root->key_count = 1;new_root->keys[0] = key;split_child(new_root, 0, tree);tree->root = new_root;insert_non_full(tree->root, key, value);} else {insert_non_full(root, key, value);}
}void insert_non_full(BPlusTreeNode *node, KeyType key, ValueType value) {if (node->leaf) {int i = node->key_count - 1;while (i >= 0 && node->keys[i] > key) {node->keys[i + 1] = node->keys[i];node->values[i + 1] = node->values[i];i--;}node->keys[i + 1] = key;node->values[i + 1] = value;node->key_count++;} else {int i = node->key_count - 1;while (i >= 0 && node->keys[i] > key) {i--;}i++;if (node->children[i]->key_count == MAX_KEYS) {split_child(node, i, tree);if (node->keys[i] < key) {i++;}}insert_non_full(node->children[i], key, value);}
}void split_child(BPlusTreeNode *parent, int index, BPlusTree *tree) {BPlusTreeNode *full_node = parent->children[index];BPlusTreeNode *new_node = create_node(full_node->leaf);parent->key_count++;for (int i = 0; i < MAX_KEYS / 2; i++) {new_node->keys[i] = full_node->keys[i + MAX_KEYS / 2 + 1];if (!full_node->leaf) {new_node->children[i] = full_node->children[i + MAX_KEYS / 2 + 1];} else {new_node->values[i] = full_node->values[i + MAX_KEYS / 2 + 1];}}if (!full_node->leaf) {new_node->children[MAX_KEYS / 2] = full_node->children[MAX_KEYS + 1];}full_node->key_count = MAX_KEYS / 2;parent->keys[index] = full_node->keys[MAX_KEYS / 2];parent->children[index + 1] = new_node;if (index == parent->key_count - 1) {parent->key_count++;} else {for (int j = parent->key_count; j > index + 1; j--) {parent->keys[j] = parent->keys[j - 1];parent->children[j + 1] = parent->children[j];}}
}BPlusTreeNode* search(BPlusTree *tree, KeyType key, int *index) {BPlusTreeNode *node = tree->root;while (!node->leaf) {*index = 0;while (*index < node->key_count && node->keys[*index] < key) {(*index)++;}node = node->children[*index];}*index = -1;for (int i = 0; i < node->key_count; i++) {if (node->keys[i] == key) {*index = i;return node;}}return NULL;
}void traverse(BPlusTreeNode *node, int depth) {for (int i = 0; i < node->key_count; i++) {printf("%*s%d ", depth * 2, "", node->keys[i]);if (!node->leaf) {traverse(node->children[i], depth + 1);}}if (!node->leaf && node->key_count > 0) {traverse(node->children[node->key_count], depth + 1);}
}void free_tree(BPlusTree *tree) {free_node(tree->root);free(tree);
}void free_node(BPlusTreeNode *node) {if (node != NULL) {if (!node->leaf) {for (int i = 0; i <= node->key_count; i++) {free_node(node->children[i]);}} else {free(node->values);}free(node);}
}

main.c

int main() {BPlusTree tree;tree.root = create_node(true);  // 创建一个空的根节点(叶子节点)// 插入一些键值对insert(&tree, 10, 100);insert(&tree, 20, 200);insert(&tree, 5, 50);insert(&tree, 15, 150);insert(&tree, 25, 250);// 查找键值并打印对应的值KeyType keysToFind[] = {5, 10, 15, 20, 25, 30};  // 30是一个不存在的键值for (int i = 0; i < sizeof(keysToFind) / sizeof(keysToFind[0]); i++) {ValueType *value = search(&tree, keysToFind[i]);if (value) {printf("Found key %d with value %d\n", keysToFind[i], *value);} else {printf("Key %d not found\n", keysToFind[i]);}}// 省略了释放内存的代码...return 0;
}

说明

  1. 数据结构:我们定义了B+树节点和B+树本身的数据结构,包括键值、子节点指针、链表连接指针、是否为叶子节点的标志以及叶子节点中的值数组。
  2. 辅助函数:创建新节点、分裂子节点和查找叶子节点的辅助函数实现,这些函数对于实现插入和查找操作是必要的。
  3. 插入操作:插入操作由于相对复杂。它需要处理节点的分裂,并可能递归地向上调整树的结构。
  4. 查找操作:查找操作沿着树向下搜索,直到找到目标键值或确定其不存在。在叶子节点中查找键值并返回对应的值。
  5. 应用实例:在main函数中,我们创建了一个B+树,插入了一些键值对,然后查找这些键值并打印对应的值。

请注意,由于篇幅限制和简化目的,省略了删除操作和一些错误处理。在实际应用中,需要实现完整的插入、删除和查找操作,并添加适当的错误处理。此外,这个实现中的B+树是内存中的数据结构,如果要在磁盘上实现B+树,还需要考虑磁盘IO操作和缓存管理。

总结

B+树通过将所有数据集中在叶子节点并连接叶子节点,优化了范围查询和顺序扫描的效率,特别适合数据库索引和文件系统等场景。其C语言实现需要考虑节点分裂、合并等复杂操作,但可以显著提高数据访问效率。


http://www.ppmy.cn/news/1571074.html

相关文章

练习题(2.10)

问题描述 有一个 SNS 被 NN 个用户使用&#xff0c;他们的编号从 11 到 NN。 在这个 SNS 中&#xff0c;两个用户可以成为朋友。 友谊是双向的&#xff1b;如果用户 X 是用户 Y 的朋友&#xff0c;那么用户 Y 也一定是用户 X 的朋友。 目前&#xff0c;在 SNS 上有 MM 对朋…

java-list深入理解(流程图)

List源码学习: 此篇文章使用流程图和源码方式,理解List的源码,方便记忆 核心逻辑流程图: #mermaid-svg-BBrPrDuqUdLMtHvj {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-BBrPrDuqUdLMtHvj .error-icon{fill:#…

数据结构——红黑树的实现

目录 1 红黑树的概念 1.1 红黑树的规则 1.2 红黑树是如何确保最长路径不超过最短路径的2倍的&#xff1f; 1.3 红黑树的效率 2 红黑树的实现 2.1 红黑树的结构 2.2 红黑树的插入 2.2.1 红黑树插入节点的大概过程 2.2.2 情况1&#xff1a;只变色&#xff0c;不旋转 2.2.3 情况…

TCP三次握手全方面详解

文章目录 (1) 三次握手各状态CLOSE状态SYN_SENT状态SYN_RECV状态ESTABLISHED状态 (2) 为什么握手时的seqnum是随机值&#xff0c;以及acknum的功能(3) 三次握手中的半连接队列&#xff08;SYN队列&#xff09;和全连接队列&#xff08;ACCEPT队列&#xff09;半连接队列全连接队…

清除el-table选中状态 clearSelection

如何在Vue应用中使用Element UI的el-table组件&#xff0c;通过this.$refs.multipleTable.clearSelection()方法来清除所有选中行的状态。适合前端开发者了解表格组件的交互操作。 // el-table绑定ref<el-table selection-change"selsChange" ref"multipl…

2024最新版Java面试题及答案,【来自于各大厂】

发现网上很多Java面试题都没有答案&#xff0c;所以花了很长时间搜集整理出来了这套Java面试题大全~ 篇幅限制就只能给大家展示小册部分内容了&#xff0c;需要完整版的及Java面试宝典小伙伴点赞转发&#xff0c;关注我后在【翻到最下方&#xff0c;文尾点击名片】即可免费获取…

【机器学习】超参数的选择,以kNN算法为例

分类准确度 一、摘要二、超参数的概念三、调参的方法四、实验搜索超参数五、扩展搜索范围六、考虑距离权重的kNN算法七、距离的计算方法及代码实现八、明可夫斯基距离的应用九、网格搜索超参数 一、摘要 本博文讲解了机器学习中的超参数问题&#xff0c;以K近邻算法为例&#…

Java WORD和PDF互相转换以及数据填充示例

最近碰到一个需求&#xff0c;就是有一些 WORD 或者 PDF 的模板&#xff0c;然后根据用户填入的数据填充进去&#xff0c;还要根据用户选择要 PDF 还是 WORD 下载下来 所以综合下来就是两个功能&#xff1a; 1.WORD 和 PDF 模板填充 2.WORD 和 PDF 互相转换 直接上代码 首先…