B树原理与C语言实现

1. 引言

B树是一种自平衡的多路搜索树,广泛应用于数据库和文件系统中。它通过让每个节点容纳多个关键字和多个子节点,显著降低树的高度,从而减少磁盘I/O次数。本文将从B树的定义、性质、基本操作入手,结合C语言代码,帮助读者深入理解B树的原理与实现。

2. 什么是B树

B树是一种多路平衡搜索树,其每个节点可以拥有两个以上的子节点。与二叉搜索树不同,B树通过增加节点的分支数来降低树的高度,使得在大量数据场景下,查找、插入和删除操作所需的磁盘访问次数更少。

B树通常用于磁盘等块存储设备,因为一次磁盘I/O可以读取一个完整的节点,节点越大,单次I/O能处理的关键字越多,效率越高。

3. B树的性质

一棵阶数为 m 的B树满足以下性质:

  • 每个节点最多包含 m 个子节点。
  • 除根节点外,每个非叶子节点至少包含 ⌈m/2⌉ 个子节点。
  • 根节点至少有两个子节点(除非它是叶子节点)。
  • 有 k 个子节点的非叶子节点包含 k-1 个关键字。
  • 所有叶子节点位于同一层,树始终保持平衡。

这些性质保证了B树的高度始终维持在 O(log_m n) 级别,其中 n 为关键字总数。

4. B树的基本操作

4.1 查找

B树的查找过程与二叉搜索树类似,但每个节点需要遍历多个关键字。从根节点开始,在节点内部顺序或二分查找目标关键字;若找到则返回,否则根据关键字大小进入对应的子树继续查找,直到叶子节点。

4.2 插入

插入操作首先执行查找,找到合适的叶子节点位置。若叶子节点未满,直接插入关键字;若节点已满,则需要进行节点分裂,将中间关键字提升到父节点,并递归处理父节点可能发生的溢出。

4.3 删除

删除操作较为复杂。若删除的关键字位于叶子节点,直接删除;若位于内部节点,则需要用前驱或后继关键字替换,然后递归删除。删除后若节点关键字数量低于下限,需要通过借位或合并操作恢复平衡。

5. C语言实现

下面给出一个简化的B树C语言实现,重点展示节点的定义、查找和插入逻辑。

c 复制代码
#include <stdio.h>
#include <stdlib.h>

#define M 3  // B树的阶数

typedef struct BTreeNode {
    int keys[M - 1];          // 关键字数组
    struct BTreeNode *child[M]; // 子节点指针数组
    int n;                    // 当前关键字数量
    int leaf;                 // 是否为叶子节点
} BTreeNode;

// 创建新节点
BTreeNode* createNode(int leaf) {
    BTreeNode *node = (BTreeNode*)malloc(sizeof(BTreeNode));
    node->leaf = leaf;
    node->n = 0;
    for (int i = 0; i < M; i++) {
        node->child[i] = NULL;
    }
    return node;
}

// 在节点内查找关键字位置
int searchKey(BTreeNode *node, int key) {
    int idx = 0;
    while (idx < node->n && key > node->keys[idx]) {
        idx++;
    }
    return idx;
}

// 在B树中查找关键字
BTreeNode* search(BTreeNode *root, int key) {
    if (root == NULL) return NULL;
    int idx = searchKey(root, key);
    if (idx < root->n && root->keys[idx] == key) {
        return root;
    }
    if (root->leaf) return NULL;
    return search(root->child[idx], key);
}

// 分裂满节点
void splitChild(BTreeNode *parent, int idx) {
    BTreeNode *child = parent->child[idx];
    BTreeNode *newNode = createNode(child->leaf);
    newNode->n = M / 2 - 1;

    for (int i = 0; i < M / 2 - 1; i++) {
        newNode->keys[i] = child->keys[i + M / 2];
    }
    if (!child->leaf) {
        for (int i = 0; i < M / 2; i++) {
            newNode->child[i] = child->child[i + M / 2];
        }
    }
    child->n = M / 2 - 1;

    for (int i = parent->n; i > idx; i--) {
        parent->child[i + 1] = parent->child[i];
    }
    parent->child[idx + 1] = newNode;

    for (int i = parent->n - 1; i >= idx; i--) {
        parent->keys[i + 1] = parent->keys[i];
    }
    parent->keys[idx] = child->keys[M / 2 - 1];
    parent->n++;
}

// 向非满节点插入关键字
void insertNonFull(BTreeNode *node, int key) {
    int idx = node->n - 1;
    if (node->leaf) {
        while (idx >= 0 && key < node->keys[idx]) {
            node->keys[idx + 1] = node->keys[idx];
            idx--;
        }
        node->keys[idx + 1] = key;
        node->n++;
    } else {
        idx = searchKey(node, key);
        if (node->child[idx]->n == M - 1) {
            splitChild(node, idx);
            if (key > node->keys[idx]) {
                idx++;
            }
        }
        insertNonFull(node->child[idx], key);
    }
}

// B树插入入口
BTreeNode* insert(BTreeNode *root, int key) {
    if (root == NULL) {
        root = createNode(1);
        root->keys[0] = key;
        root->n = 1;
        return root;
    }
    if (root->n == M - 1) {
        BTreeNode *newRoot = createNode(0);
        newRoot->child[0] = root;
        splitChild(newRoot, 0);
        insertNonFull(newRoot, key);
        return newRoot;
    }
    insertNonFull(root, key);
    return root;
}

// 中序遍历B树
void inorder(BTreeNode *root) {
    if (root == NULL) return;
    for (int i = 0; i < root->n; i++) {
        if (!root->leaf) {
            inorder(root->child[i]);
        }
        printf("%d ", root->keys[i]);
    }
    if (!root->leaf) {
        inorder(root->child[root->n]);
    }
}

int main() {
    BTreeNode *root = NULL;
    int keys[] = {10, 20, 5, 6, 12, 30, 7, 17};
    int n = sizeof(keys) / sizeof(keys[0]);

    for (int i = 0; i < n; i++) {
        root = insert(root, keys[i]);
    }

    printf("中序遍历结果:");
    inorder(root);
    printf("\n");

    int target = 12;
    BTreeNode *result = search(root, target);
    if (result != NULL) {
        printf("找到关键字 %d\n", target);
    } else {
        printf("未找到关键字 %d\n", target);
    }

    return 0;
}

上述代码实现了B树的创建、查找、插入和中序遍历。其中 splitChild 负责在节点满时进行分裂,insertNonFull 处理向非满节点插入的逻辑,insert 作为入口处理根节点分裂的情况。

6. 代码说明

在实现中,M 表示B树的阶数,这里取 3,即每个节点最多有 3 个子节点、2 个关键字。当节点关键字数量达到 M-1 时,插入新关键字会触发分裂操作,中间关键字上移到父节点,从而维持B树的平衡性质。

查找操作的时间复杂度为 O(log_m n),插入操作在最坏情况下需要从叶子节点向上分裂到根节点,但整体仍保持对数级别。

7. 总结

B树通过多路分支和节点分裂机制,在保持树平衡的同时大幅降低树的高度,非常适合磁盘存储场景。本文介绍了B树的核心性质、基本操作,并给出了完整的C语言实现。读者可以在此基础上进一步扩展删除操作、磁盘页管理以及B+树等变体,以应对更复杂的实际需求。

相关推荐
零依赖极客1 小时前
《30 天手搓 ARM 架构零依赖纯 C 推理引擎》课程介绍与大纲
c语言·开发语言·arm开发·人工智能·嵌入式硬件·ai编程
蓝速科技1 小时前
酒店门店 AI 数字人前台场景适配与落地指南
大数据·运维·数据结构·数据库·人工智能·科技
LuminousCPP1 小时前
数据结构-排序(三):快速排序进阶|挖坑法、双指针交换与手动栈非递归实现
c语言·数据结构·笔记·算法·排序算法
linx2952 小时前
单元三 · 那 C 的底层知识怎么办
c语言·开发语言·数据结构·c++·嵌入式硬件
zander2582 小时前
LeetCode 128. 最长连续序列
数据结构·算法
linx2953 小时前
单元四 · 对称认知·上:内存与指针
c语言·开发语言·数据结构·嵌入式硬件·算法
-dzk-3 小时前
【二叉树】LC 236.二叉树的最近公共祖先
数据结构·二叉树
午彦琳3 小时前
2026.9.11
数据结构·python·算法