c++如何实现一个b-tree数据结构

来源:网站建设作者:森沢头衔:网络博主
导读:本期聚焦于小伙伴创作的《c++如何实现一个b-tree数据结构》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《c++如何实现一个b-tree数据结构》有用,将其分享出去将是对创作者最好的鼓励。

B树是一种平衡的多分树,每个节点可以包含多个关键字和多个子节点,所有叶子节点位于同一层,能够保持数据有序且查询效率稳定。它的最大特点是通过增大节点的关键字数量,降低树的高度,从而减少磁盘访问次数,因此被广泛应用在数据库索引、文件系统索引等场景中。下面我们用C++实现一个支持插入和查找操作的B树。

c++如何实现一个b-tree数据结构

B树的核心特性

实现B树前需要先明确它的核心约束,假设B树的阶数为m,即每个节点最多包含m-1个关键字和m个子节点,那么需要满足以下规则:

  • 根节点至少包含1个关键字,除非根节点是叶子节点
  • 非根节点至少包含ceil(m/2)-1个关键字
  • 所有叶子节点位于同一层,且不包含子节点
  • 节点内的关键字按升序排列,子节点的关键字范围在相邻两个父节点关键字之间

节点结构设计

我们首先定义B树的节点结构,每个节点需要存储关键字数组、子节点指针数组、当前关键字数量以及是否为叶子节点的标识:

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>

using namespace std;

// B树节点模板类,T为关键字类型,默认支持int
template <typename T = int>
class BTreeNode {
public:
    // 关键字数组,最多存储m-1个
    vector<T> keys;
    // 子节点指针数组,最多存储m个
    vector<BTreeNode*> children;
    // 当前节点的关键字数量
    int keyNum;
    // 是否为叶子节点
    bool isLeaf;
    // B树的阶数
    int m;

    BTreeNode(int order, bool leaf) {
        m = order;
        isLeaf = leaf;
        keyNum = 0;
        // 预分配空间,避免频繁扩容
        keys.reserve(m);
        children.reserve(m + 1);
    }

    // 在节点中查找关键字的位置,返回第一个大于等于key的索引
    int findKey(const T& key) {
        int idx = 0;
        while (idx < keyNum && keys[idx] < key) {
            idx++;
        }
        return idx;
    }
};

B树类的整体定义

接下来定义B树的主体类,包含根节点、阶数,以及插入、查找、分裂等核心方法:

template <typename T = int>
class BTree {
private:
    // B树的根节点
    BTreeNode<T>* root;
    // B树的阶数
    int m;

public:
    BTree(int order) {
        m = order;
        root = nullptr;
    }

    // 析构函数,释放所有节点内存
    ~BTree() {
        destroyTree(root);
    }

    // 插入关键字
    void insert(const T& key);
    // 查找关键字,返回是否找到
    bool search(const T& key);
    // 打印B树结构,用于调试
    void printTree();

private:
    // 分裂满子节点
    void splitChild(BTreeNode<T>* parent, int childIdx);
    // 向非满节点插入关键字
    void insertNonFull(BTreeNode<T>* node, const T& key);
    // 递归查找关键字
    bool searchRecursive(BTreeNode<T>* node, const T& key);
    // 递归打印树结构
    void printRecursive(BTreeNode<T>* node, int level);
    // 递归释放节点内存
    void destroyTree(BTreeNode<T>* node);
};

核心方法实现

查找操作实现

查找操作从根节点开始,在当前节点的关键字数组中查找目标值,如果找到则返回true;如果当前节点是叶子节点且未找到则返回false;否则根据关键字大小进入对应的子节点继续查找:

template <typename T>
bool BTree<T>::searchRecursive(BTreeNode<T>* node, const T& key) {
    if (node == nullptr) {
        return false;
    }
    // 找到第一个大于等于key的关键字索引
    int idx = node->findKey(key);
    // 如果刚好等于key,说明找到
    if (idx < node->keyNum && node->keys[idx] == key) {
        return true;
    }
    // 如果是叶子节点,说明没找到
    if (node->isLeaf) {
        return false;
    }
    // 否则进入对应的子节点继续查找
    return searchRecursive(node->children[idx], key);
}

template <typename T>
bool BTree<T>::search(const T& key) {
    return searchRecursive(root, key);
}

节点分裂操作

当插入关键字导致节点关键字数量达到m-1时,需要对该节点进行分裂。分裂时取中间的关键字提升到父节点,原节点拆分为两个子节点:

template <typename T>
void BTree<T>::splitChild(BTreeNode<T>* parent, int childIdx) {
    BTreeNode<T>* child = parent->children[childIdx];
    // 创建新的右子节点
    BTreeNode<T>* newChild = new BTreeNode<T>(m, child->isLeaf);
    // 中间关键字的索引,提升到父节点
    int mid = (m - 1) / 2;
    T midKey = child->keys[mid];

    // 将原节点的后半部分关键字移动到新节点
    for (int i = mid + 1; i < child->keyNum; i++) {
        newChild->keys.push_back(child->keys[i]);
        newChild->keyNum++;
    }
    // 如果不是叶子节点,将原节点的后半部分子节点移动到新节点
    if (!child->isLeaf) {
        for (int i = mid + 1; i <= child->keyNum; i++) {
            newChild->children.push_back(child->children[i]);
        }
    }
    // 原节点保留前半部分关键字,更新关键字数量
    child->keyNum = mid;
    child->keys.resize(mid);
    if (!child->isLeaf) {
        child->children.resize(mid + 1);
    }

    // 将中间关键字插入父节点
    parent->keys.insert(parent->keys.begin() + childIdx, midKey);
    parent->keyNum++;
    // 将新子节点插入父节点的子节点数组
    parent->children.insert(parent->children.begin() + childIdx + 1, newChild);
}

插入操作实现

插入操作首先判断根节点是否为空,为空则创建根节点;如果根节点已满,先分裂根节点再插入;否则调用非满节点插入方法:

template <typename T>
void BTree<T>::insertNonFull(BTreeNode<T>* node, const T& key) {
    int idx = node->findKey(key);
    // 如果是叶子节点,直接插入关键字
    if (node->isLeaf) {
        node->keys.insert(node->keys.begin() + idx, key);
        node->keyNum++;
    } else {
        // 如果子节点已满,先分裂
        if (node->children[idx]->keyNum == m - 1) {
            splitChild(node, idx);
            // 分裂后需要判断插入到哪个子节点
            if (key > node->keys[idx]) {
                idx++;
            }
        }
        insertNonFull(node->children[idx], key);
    }
}

template <typename T>
void BTree<T>::insert(const T& key) {
    // 根节点为空,直接创建根节点
    if (root == nullptr) {
        root = new BTreeNode<T>(m, true);
        root->keys.push_back(key);
        root->keyNum = 1;
        return;
    }
    // 根节点已满,先分裂根节点
    if (root->keyNum == m - 1) {
        BTreeNode<T>* newRoot = new BTreeNode<T>(m, false);
        newRoot->children.push_back(root);
        splitChild(newRoot, 0);
        // 新的根节点有两个子节点,判断插入到哪个子节点
        int i = 0;
        if (newRoot->keys[0] < key) {
            i++;
        }
        insertNonFull(newRoot->children[i], key);
        root = newRoot;
    } else {
        insertNonFull(root, key);
    }
}

辅助方法实现

打印和内存释放的辅助方法实现如下:

template <typename T>
void BTree<T>::printRecursive(BTreeNode<T>* node, int level) {
    if (node == nullptr) {
        return;
    }
    cout << "Level " << level << ": ";
    for (int i = 0; i < node->keyNum; i++) {
        cout << node->keys[i] << " ";
    }
    cout << endl;
    if (!node->isLeaf) {
        for (int i = 0; i <= node->keyNum; i++) {
            printRecursive(node->children[i], level + 1);
        }
    }
}

template <typename T>
void BTree<T>::printTree() {
    printRecursive(root, 0);
}

template <typename T>
void BTree<T>::destroyTree(BTreeNode<T>* node) {
    if (node == nullptr) {
        return;
    }
    if (!node->isLeaf) {
        for (int i = 0; i <= node->keyNum; i++) {
            destroyTree(node->children[i]);
        }
    }
    delete node;
}

测试示例

我们可以通过以下代码测试B树的基本功能:

int main() {
    // 创建阶数为3的B树
    BTree<int> btree(3);
    // 插入测试数据
    vector<int> testData = {10, 20, 5, 6, 12, 30, 7, 17};
    for (int num : testData) {
        btree.insert(num);
        cout << "插入 " << num << " 后,B树结构:" << endl;
        btree.printTree();
        cout << "------------------------" << endl;
    }

    // 测试查找功能
    cout << "查找 6: " << (btree.search(6) ? "找到" : "未找到") << endl;
    cout << "查找 25: " << (btree.search(25) ? "找到" : "未找到") << endl;
    return 0;
}

注意事项

上述实现是基础版本的B树,仅支持插入和查找操作,实际生产环境中还需要实现删除操作,删除操作的逻辑比插入更复杂,需要处理节点关键字数量不足时的合并或借调操作。另外,代码中的关键字类型默认是int,可以通过模板支持其他可比较的类型,使用时只需要保证类型支持小于、大于等比较操作即可。

B_treeC++数据结构节点插入节点分裂修改时间:2026-07-09 11:51:22

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。