B+树详解与实现

news2025/1/14 1:16:11

B+树详解与实现

  • 一、引言
  • 二、B+树的定义
  • 三、B+树的插入
  • 四、B+树的删除
  • 五、B+树的查找效率
  • 六、B+树与B树的区别和联系

一、引言

B+树是一种树数据结构,通常用于数据库和操作系统的文件系统中。它的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+树元素自底向上插入,这与二叉树恰好相反。B+树在节点访问时间远远超过节点内部访问的时候,比可作为替代的实现有着实在的优势。通过最大化在每个内部节点内的子节点的数目减少树的高度,平衡操作不经常发生,而且效率增加了。这种价值得以确立通常需要每个节点在次级存储中占据完整的磁盘块或近似的大小。B+树是B树的一种变形形式,B+树上的叶子结点存储关键字以及相应记录的地址,叶子结点以上各层作为索引使用。一棵m阶的B+树和m阶的B树的差异在于:

  1. 有n棵子树的节点中含有n个关键字(B树中是n-1个)。
  2. 所有的叶子节点中包含了全部关键字的信息以及指向这些关键字记录的指针,且叶子节点本身依关键字的大小从小到大顺序链接。
  3. 所有的非叶子节点可以看成是索引部分,节点中仅含有其子树(根节点)中的最大(或最小)关键字。

通常在B+树上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点。对B+树进行查找时,通常从根节点开始,当找到叶子节点后,再在其中进行查找,直到找到对应的数据或指针。与B-树相比,B+树在非叶子节点上是不存储数据的,只存储它的孩子节点的最大(或最小)关键字信息和指向其子节点的指针信息,这样使得B+树非叶子节点所能容纳的孩子节点信息更多,树的高度相对比B-树小,在查找时所需要的IO操作次数也比B-树小。

在这里插入图片描述

二、B+树的定义

一颗m阶的B+树和m阶的B树的差异在于:

  1. 有n棵子树的节点中含有n个关键字。(B树中是n-1个)
  2. 所有的叶子节点中包含了全部关键字的信息以及指向含这些关键字记录的指针,且叶子节点本身依关键字的大小从小到大顺序链接。
  3. 所有的非叶子节点可以看成是索引部分,节点中仅含有其子树(根节点)中的最大(或最小)关键字。

三、B+树的插入

B+树的插入首先在叶子节点中进行,若插入后叶子节点中的关键字个数大于m,则需要进行分裂操作。分裂需要两个步骤:

  1. 节点分裂:将原节点中的关键字和子节点平均分配到两个新的节点中,原节点中的第m/2个(下取整,下同)关键字上升到其父节点中(父节点中关键字的个数加1),若没有父节点(原节点为根节点),则创建一个新的根节点。
  2. 调整索引:当分裂操作使得节点中关键字的个数超过m-1个时,需要对父节点进行分裂操作(即使父节点的关键字个数没有超过m-1,但只要其有子节点的关键字个数超过m-1,也需要进行分裂,以保证B+树的性质)。这个分裂过程可能会递归向上进行,甚至可能导致根节点的分裂,从而增加树的高度。

以下是插入操作的伪代码实现:

function Insert(root, key) {
    leaf = FindLeaf(root, key)
    if (leaf.keyword_count < order - 1) {
        // 插入到叶子节点
        leaf.Insert(key)
    } else {
        // 分裂叶子节点
        new_leaf, median = leaf.Split()
        parent = leaf.parent
        if (parent == null) {
            // 创建新的根节点
            new_root = CreateNewRoot(leaf, new_leaf, median)
            root = new_root
        } else {
            parent.Insert(median)
            if (parent.keyword_count > order - 1) {
                Insert(root, median) // 递归插入
            }
        }
    }
}

四、B+树的删除

B+树的删除操作稍微复杂一些,需要考虑多种情况。如果被删除的关键字位于非叶子节点,则需要用其后继节点(或前驱节点)中的最小(或最大)关键字替换,并转化为在叶子节点中的删除。如果被删除的关键字位于叶子节点,且该叶子节点中的关键字个数大于等于ceil(m/2),则可以直接删除。否则,需要考虑合并叶子节点或者从相邻节点借调关键字。以下是删除操作的伪代码实现:

function Delete(root, key) {
    node = FindNode(root, key) // 找到包含key的节点
    if (node is not a leaf) {
        // 如果不是叶子节点,找到后继节点,并用后继节点中的最小关键字替换当前节点的key,然后删除后继节点中的该关键字
        successor = FindSuccessor(node) 
        node.key = successor.min_key 
        node = successor // 转化为在叶子节点中的删除
    }
    if (node.keyword_count > ceil(m/2)) {
        node.Delete(key) // 可以直接删除
    } else {
        if (node的相邻兄弟节点的关键字个数 > ceil(m/2)) {
            // 从相邻兄弟节点借调关键字
            BorrowFromSibling(node)
        } else {
            // 合并节点
            MergeWithSibling(node)
        }
    }
}

五、B+树的查找效率

在B+树上进行查找的效率与树的高度成正比,而树的高度又与树的阶数m和包含n个关键字的B+树的层数相关。因此,通过合理设置B+树的阶数m,可以使得查找效率达到最优。在实际情况中,通常根据磁盘块的大小和关键字的平均大小来确定B+树的阶数m。

六、B+树与B树的区别和联系

B+树与B树的主要区别在于非叶子节点是否存储关键字信息。在B树中,非叶子节点既存储关键字信息,又存储子节点的指针信息;而在B+树中,非叶子节点只存储子节点的最大(或最小)关键字信息和指针信息,所有的关键字信息和相应的数据都存储在叶子节点中。这使得B+树的非叶子节点可以存储更多的子节点信息,从而降低了树的高度,提高了查找效率。另外,B+树的叶子节点之间是通过指针链接在一起的,这样便于进行范围查找和顺序访问。

七、C语言实现B+树的基本操作示例(部分代码)

为了提供一个更完整的示例,下面是一个简化的B+树插入操作的C代码实现。请注意,这个实现是为了教学目的而简化的,并不适合用于生产环境。

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

#define ORDER 4 // B+树的阶数
#define MAX_KEYS (ORDER - 1)
#define MIN_KEYS (ORDER / 2)

typedef struct BPlusTreeNode {
    int keys[MAX_KEYS];
    struct BPlusTreeNode *children[ORDER];
    struct BPlusTreeNode *next; // 用于叶子节点的链表
    int keyCount;
    bool isLeaf;
} BPlusTreeNode;

BPlusTreeNode* createNode(bool isLeaf) {
    BPlusTreeNode* newNode = (BPlusTreeNode*)malloc(sizeof(BPlusTreeNode));
    if (!newNode) {
        perror("Memory allocation failed");
        exit(EXIT_FAILURE);
    }
    newNode->keyCount = 0;
    newNode->isLeaf = isLeaf;
    newNode->next = NULL;
    for (int i = 0; i < ORDER; i++) {
        newNode->children[i] = NULL;
    }
    return newNode;
}

void insertKey(BPlusTreeNode* node, int key, BPlusTreeNode* child) {
    int i;
    for (i = node->keyCount - 1; i >= 0 && key < node->keys[i]; i--) {
        node->keys[i + 1] = node->keys[i];
        if (!node->isLeaf) {
            node->children[i + 2] = node->children[i + 1];
        }
    }
    node->keys[i + 1] = key;
    if (!node->isLeaf) {
        node->children[i + 2] = child;
    }
    node->keyCount++;
}

void splitNode(BPlusTreeNode* node, BPlusTreeNode** newNode, int* median) {
    *newNode = createNode(node->isLeaf);
    int midIndex = node->keyCount / 2;
    *median = node->keys[midIndex];
    (*newNode)->keyCount = node->keyCount - midIndex - 1;

    for (int i = 0; i < (*newNode)->keyCount; i++) {
        (*newNode)->keys[i] = node->keys[midIndex + 1 + i];
        if (node->isLeaf) {
            // If it's a leaf node, copy the next pointers as well
            (*newNode)->next = node->next;
            node->next = NULL;
        } else {
            (*newNode)->children[i] = node->children[midIndex + 1 + i];
        }
    }

    node->keyCount = midIndex + 1;
}

// Recursive insert function
void insertRecursive(BPlusTreeNode* node, int key, BPlusTreeNode* leafNode) {
    if (node->isLeaf) {
        insertKey(node, key, leafNode);
        if (node->keyCount > MAX_KEYS) {
            BPlusTreeNode* newNode;
            int median;
            splitNode(node, &newNode, &median);
            if (node->next) {
                newNode->next = node->next;
                node->next->keys[0] = median; // Set the smallest key of the next node
                node->next = newNode;
            }
        }
    } else {
        int i;
        for (i = 0; i < node->keyCount; i++) {
            if (key < node->keys[i]) {
                break;
            }
        }
        if (i < node->keyCount) {
            insertRecursive(node->children[i + 1], key, leafNode);
            if (node->children[i + 1]->keyCount > MAX_KEYS) {
                int median;
                BPlusTreeNode* newNode;
                splitNode(node->children[i + 1], &newNode, &median);
                insertKey(node, median, newNode);
            }
        } else {
            insertRecursive(node->children[i], key, leafNode);
            if (node->children[i]->keyCount > MAX_KEYS) {
                int median;
                BPlusTreeNode* newNode;
                splitNode(node->children[i], &newNode, &median);
                insertKey(node, median, newNode);
            }
        }
    }
}

void insert(BPlusTreeNode** root, int key) {
    BPlusTreeNode* leafNode = createNode(true); // Dummy leaf node for recursion
    if (*root == NULL) {
        *root = createNode(true);
        insertKey(*root, key, leafNode);
    } else {
        insertRecursive(*root, key, leafNode);
    }
}

// Main function to test the insert operation
int main() {
    BPlusTreeNode* root = NULL;
    int keys[] = {10, 20, 5, 15, 30, 7, 17, 25, 40};
    int n = sizeof(keys) / sizeof(keys[0]);

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

    // Simple traversal to print the keys in the B+ tree (only for leaves)
    BPlusTreeNode* curr = root;
    while (!curr->isLeaf) {
        curr = curr->children[0]; // Assuming the smallest key is in the leftmost leaf
    }
    printf("Leaf nodes contain: ");
    while (curr) {
        for (int i = 0; i < curr->keyCount; i++) {
            printf("%d ", curr->keys[i]);
        }
        printf("| ");
        curr = curr->next;
    }
    printf("\n");

    // Cleanup code would go here (recursively free all allocated nodes)

    return 0;
}

这个简化的示例展示了如何在B+树中插入关键字。请注意,这个实现没有处理删除操作,没有实现完整的查找功能,也没有进行错误检查或优化。此外,为了简化代码,我们假设所有叶子节点都在同一个层级上,且我们省略了叶子节点中指向记录的指针(在这个示例中不需要)。在实际应用中,B+树的实现会更加复杂,并需要考虑更多的边界情况和性能优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1639499.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

hive表基本语法

hive表基本语法 青少年是一个美好而又是一去不可再得的时期 是将来一切光明和幸福的开端 目录 hive表基本语法 1.ROW FORMAT用法 2.LOCATION用法 3.EXTERNAL用法 &#xff08;外部表&#xff09; 4.STORED AS 用法&#xff1a;设置数据存储格式 5.TBLPROPERTIES 用法 6.P…

liceo靶机复现

liceo-hackmyvm 靶机地址&#xff1a;https://hackmyvm.eu/machines/machine.php?vmLiceo 本机环境&#xff1a;NAT模式下&#xff0c;使用VirtualBox 信息收集&#xff1a; 首先局域网内探测靶机IP 发现IP为10.0.2.4 开启nmap扫描一下看看开了什么端口 扫描期间看一下web页…

随便聊一下 显控科技 控制屏 通过 RS485 接口 上位机 通讯 说明

系统搭建&#xff1a; 1、自己研发的一个小系统&#xff08;采集信号&#xff0c;将采集的信号数字化&#xff09;通过COM口&#xff0c;连接显控屏 COM3 口采用 485 协议送到显控屏&#xff08;显控科技&#xff09;的显示屏展示出来&#xff09;。 2、显控屏 将 展示的数据…

Neomodel 快速上手 构建neo4j 知识图谱

介绍 python 创建neo4j 数据通常使用py2neo&#xff0c;但是这个包 官方声明已经停止更新&#xff0c;根据neo4j网站推荐使用neomodel neomodel 使用起来很想django 中的orm&#xff0c;如果有django基础的上手很简单&#xff0c;而且neomodel 支持 neo4j 5.X版本更新维护的也…

使用 FFmpeg 推拉 RTSP 流媒体

实时流传输协议 RTSP&#xff08;Real-Time Streaming Protocol&#xff09;是 TCP/IP 协议体系中的一个应用层协议&#xff0c;由哥伦比亚大学、网景和 RealNetworks 公司提交的 IETF RFC 标准。该协议定义了一对多应用程序如何有效地通过 IP 网络传送多媒体数据。RTSP 在体系…

全栈开发之路——前端篇(3)setup和响应式数据

全栈开发一条龙——前端篇 第一篇&#xff1a;框架确定、ide设置与项目创建 第二篇&#xff1a;介绍项目文件意义、组件结构与导入以及setup的引入。 本文为该系列的第三篇&#xff0c;主要讲述Vue核心的setup语法&#xff0c;同时讲解再使用了setup后如何设置响应式数据。 辅助…

基于php+mysql+html超市商品管理系统(含论文)

博主介绍&#xff1a; 大家好&#xff0c;本人精通Java、Python、Php、C#、C、C编程语言&#xff0c;同时也熟练掌握微信小程序、Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验&#xff0c;能够为学生提供各类…

Java-异常处理-处理RuntimeException(2/2)

下面有一个简单的整数除法运算程序&#xff0c;输入两个整数作为被除数和除数&#xff0c;在正常情况下&#xff0c;会输出两数相除的整数商。 import java.util.Scanner; public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.i…

unity制作app(3)--gps定位

1.unity中定位Unity之GPS定位&#xff08;高德解析&#xff09;_unity gps定位-CSDN博客 代码需要稍微修改一下&#xff0c;先把脚本绑到一个button上试一试&#xff01; 2.先去高德地图认证&#xff08;app定位&#xff09; 创建应用和 Key-Web服务 API | 高德地图API (ama…

【Java】HOT100 贪心算法

目录 理论基础 一、简单贪心 LeetCode455&#xff1a;分发饼干 二、中等贪心 2.1 序列问题 LeetCode376&#xff1a;摆动序列 2.2 贪心股票问题 LeetCode121&#xff1a;买卖股票的最佳时机 LeetCode121&#xff1a;买卖股票的最佳时机ii 2.3 两个维度权衡问题 LeetCode135&…

GitHub Copilot Workspace:欢迎进入原生Copilot开发环境

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

华为Pura70发布,供应链公司进入静默保密期

保密措施&#xff1a;与华为Pura70发布相关的供应链公司在产品发布前后处于静默保密期。这可能是由于华为对于手机供应链的一些信息处于保密状态&#xff0c;尤其是关于麒麟芯片的代工厂商等敏感信息。这种保密措施有助于保持产品的神秘感&#xff0c;调动用户的好奇心&#xf…

mac电脑关于ios端的appium真机自动化测试环境搭建

一、app store 下载xcode,需要登录apple id 再开始下载 二、安装homebrew 1、终端输入命令&#xff1a; curl -fsSL <https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh>如果不能直接安装&#xff0c;而是出现了很多内容&#xff0c;那么这个时候不要着急&…

MFC 列表控件修改实例(源码下载)

1、本程序基于前期我的博客文章《MFC下拉菜单打钩图标存取实例&#xff08;源码下载&#xff09;》 2、程序功能选中列表控件某一项&#xff0c;修改这一项的按钮由禁止变为可用&#xff0c;双击这个按钮弹出对话框可对这一项的记录数据进行修改&#xff0c;点击确定保存修改数…

《R语言与农业数据统计分析及建模》学习——数字图像处理

数字图像处理&#xff08;digital image processing&#xff09;又称计算机图像处理&#xff0c;它是指将图像信号转换成数字信号并利用数字图像处理计算机对其进行处理的过程。 常见的数字图像处理是通过计算机对图像进行去除噪声、增强、复原、分割、提取特征等处理。 R语言…

信息管理与信息系统就业方向及前景分析

信息管理与信息系统(IMIS)专业的就业方向十分广泛&#xff0c;包含计算机方向、企业信息化管理、数据处理和数据分析等&#xff0c;随着大数据、云计算、人工智能、物联网等技术的兴起&#xff0c;对能够处理复杂信息系统的专业人才需求激增&#xff0c;信息管理与信息系统就业…

数据分析:基于DESeq2的转录组功能富集分析

介绍 DESeq2常用于识别差异基因&#xff0c;它主要使用了标准化因子标准化数据&#xff0c;再根据广义线性模型判别组间差异&#xff08;组间残差是否显著判断&#xff09;。在获取差异基因结果后&#xff0c;我们可以进行下一步的富集分析&#xff0c;常用方法有基于在线网站…

Mac 安装 JDK21 流程

一、下载JDK21 访问Oracle官方网站或选择OpenJDK作为替代品。Oracle JDK从11版本开始是商业的&#xff0c;可能需要支付费用。OpenJDK是一个免费开源选项。 Oracle JDK官方网站&#xff1a;Oracle JDK Downloads OpenJDK官方网站&#xff1a;OpenJDK Downloads 这里以JDK21为…

Servlet详解(从xml到注解)

文章目录 概述介绍作用 快速入门Servelt的执行原理执行流程&#xff1a;执行原理 生命周期概述API 服务器启动&#xff0c;立刻加载Servlet对象(理解)实现Servlet方式(三种)实现Servlet接口实现GenericServlet抽象类&#xff0c;只重写service方法实现HttpServlet实现类实现Htt…

uni-app scroll-view隐藏滚动条的小细节 兼容主流浏览器

开端 想写个横向滚动的列表适配浏览器&#xff0c;主要就是隐藏一下滚动条在手机上美观一点。 但是使用uni-app官方文档建议的::-webkit-scrollbar在目标标签时发现没生效。 .scroll-view_H::-webkit-scrollbar{display: none; }解决 F12看了一下&#xff0c;原来编译到浏览…