数据结构实战:用B树实现数据库索引详解

引言

数据库索引是提升查询效率的核心组件,而B树因其高效的磁盘I/O特性和平衡的树结构,成为数据库索引的基石。本文将深入讲解B树的原理及其在数据库索引中的应用,并通过C语言实现一个支持插入和查询的B树索引系统,帮助读者理解底层机制。


一、数据库索引基础

1.1 为什么需要索引?

  • 加速查询:避免全表扫描,快速定位数据。

  • 支持范围查询:B树索引天然支持有序遍历。

  • 唯一性约束:通过唯一索引保证数据唯一性。

1.2 哈希表 vs B树

特性哈希表B树
查询速度O(1)O(log n)
范围查询不支持支持
磁盘友好性差(随机访问)优(顺序块访问)
适用场景精确查找(如缓存)数据库索引、文件系统

二、B树核心原理

2.1 B树的定义

B树是一种平衡多路搜索树,具有以下特性:

  • 每个节点最多包含M-1个键和M个子节点(M为阶数)。

  • 根节点至少包含1个键(除非为空树)。

  • 非根节点至少包含ceil(M/2)-1个键。

  • 所有叶子节点位于同一层。

2.2 B树的操作

2.2.1 查找

从根节点开始,逐层比较键值,直到找到目标或到达叶子节点。

2.2.2 插入
  1. 查找插入位置。

  2. 若节点未满,直接插入。

  3. 若节点已满,分裂节点并向上递归处理。

2.2.3 删除

需处理节点合并和键重分配,较为复杂(本文暂不实现)。


三、C语言实现B树索引

3.1 定义B树结构

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define M 3 // B树的阶数(每个节点最多M-1个键)

typedef struct BTreeNode {
    int keys[M - 1];        // 键数组
    struct BTreeNode* children[M]; // 子节点指针数组
    int num_keys;           // 当前键的数量
    bool is_leaf;           // 是否为叶子节点
} BTreeNode;

// 创建新节点
BTreeNode* create_node(bool is_leaf) {
    BTreeNode* node = (BTreeNode*)malloc(sizeof(BTreeNode));
    node->num_keys = 0;
    node->is_leaf = is_leaf;
    for (int i = 0; i < M; i++) {
        node->children[i] = NULL;
    }
    return node;
}

3.2 插入操作实现

// 分裂子节点
void split_child(BTreeNode* parent, int index) {
    BTreeNode* child = parent->children[index];
    BTreeNode* new_node = create_node(child->is_leaf);
    new_node->num_keys = (M - 1) / 2;

    // 移动右半部分键到新节点
    for (int i = 0; i < new_node->num_keys; i++) {
        new_node->keys[i] = child->keys[i + (M / 2)];
    }

    // 移动子节点指针(若非叶子节点)
    if (!child->is_leaf) {
        for (int i = 0; i <= new_node->num_keys; i++) {
            new_node->children[i] = child->children[i + (M / 2)];
        }
    }
    child->num_keys = (M - 1) / 2 - 1;

    // 调整父节点
    for (int i = parent->num_keys; i > index; i--) {
        parent->children[i + 1] = parent->children[i];
    }
    parent->children[index + 1] = new_node;
    parent->keys[index] = child->keys[(M - 1) / 2 - 1];
    parent->num_keys++;
}

// 插入键到未满节点
void insert_non_full(BTreeNode* node, int key) {
    int i = node->num_keys - 1;
    if (node->is_leaf) {
        while (i >= 0 && key < node->keys[i]) {
            node->keys[i + 1] = node->keys[i];
            i--;
        }
        node->keys[i + 1] = key;
        node->num_keys++;
    } else {
        while (i >= 0 && key < node->keys[i]) {
            i--;
        }
        i++;
        if (node->children[i]->num_keys == M - 1) {
            split_child(node, i);
            if (key > node->keys[i]) {
                i++;
            }
        }
        insert_non_full(node->children[i], key);
    }
}

// 插入键(入口函数)
void insert(BTreeNode** root, int key) {
    BTreeNode* root_node = *root;
    if (root_node->num_keys == M - 1) {
        BTreeNode* new_root = create_node(false);
        new_root->children[0] = root_node;
        *root = new_root;
        split_child(new_root, 0);
        insert_non_full(new_root, key);
    } else {
        insert_non_full(root_node, key);
    }
}

3.3 查找操作实现

// 查找键
bool search(BTreeNode* node, int key) {
    int i = 0;
    while (i < node->num_keys && key > node->keys[i]) {
        i++;
    }
    if (i < node->num_keys && key == node->keys[i]) {
        return true;
    } else if (node->is_leaf) {
        return false;
    } else {
        return search(node->children[i], key);
    }
}

3.4 测试代码

int main() {
    BTreeNode* root = create_node(true); // 初始为叶子节点

    // 插入测试数据
    insert(&root, 10);
    insert(&root, 20);
    insert(&root, 5);
    insert(&root, 15);
    insert(&root, 30);

    // 查找测试
    printf("Search 15: %s\n", search(root, 15) ? "Found" : "Not Found"); // Found
    printf("Search 25: %s\n", search(root, 25) ? "Found" : "Not Found"); // Not Found

    return 0;
}

四、B树在数据库中的应用

4.1 磁盘页对齐

  • B树节点大小通常与磁盘页(如4KB)对齐,减少I/O次数。

  • 每个节点存储多个键,充分利用块设备特性。

4.2 范围查询优化

  • 中序遍历B树可直接获取有序数据。

  • 支持WHERE age BETWEEN 20 AND 30类查询。


五、总结

  • B树优势:平衡性、高扇出、磁盘友好,适合数据库索引。

  • 核心操作:插入时分裂节点保证平衡,查找效率O(log n)。

  • 扩展方向:实现删除操作、支持持久化存储、优化为B+树。

$(function(){ $.fn.extend({ SimpleTree:function(options){ //初始化参数 var option = $.extend({ click:function(a){ } },options); option.tree=this; /* 在参数对象中添加对当前菜单树的引用,以便在对象中使用该菜单树 */ option._init=function(){ /* * 初始化菜单展开状态,以及分叉节点的样式 */ this.tree.find("ul ul").hide(); /* 隐藏所有子级菜单 */ this.tree.find("ul ul").prev("li").removeClass("open"); /* 移除所有子级菜单父节点的 open 样式 */ this.tree.find("ul ul[show='true']").show(); /* 显示 show 属性为 true 的子级菜单 */ this.tree.find("ul ul[show='true']").prev("li").addClass("open"); /* 添加 show 属性为 true 的子级菜单父节点的 open 样式 */ }/* option._init() End */ /* 设置所有超链接不响应单击事件 */ this.find("a").click(function(){ $(this).parent("li").click(); return false; }); /* 菜单项 接受单击 */ this.find("li").click(function(){ /* * 当单击菜单项 * 1.触发用户自定义的单击事件,将该 标签中的第一个超链接做为参数传递过去 * 2.修改当前菜单项所属的子菜单的显示状态(如果等于 true 将其设置为 false,否则将其设置为 true) * 3.重新初始化菜单 */ option.click($(this).find("a")[0]); /* 触发单击 */ /* * 如果当前节点下面包含子菜单,并且其 show 属性的值为 true,则修改其 show 属性为 false * 否则修改其 show 属性为 true */ /* if($(this).next("ul").attr("show")=="true"){ $(this).next("ul").attr("show","false"); }else{ $(this).next("ul").attr("show","true"); }*/ /* 初始化菜单 */ option._init(); }); /* 设置所有父节点样式 */ this.find("ul").prev("li").addClass("folder"); /* 设置节点“是否包含子节点”属性 */ this.find("li").find("a").attr("hasChild",false); this.find("ul").prev("li").find("a").attr("hasChild",true); /* 初始化菜单 */ option._init(); }/* SimpleTree Function End */ }); });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值