B+树详解与实现

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.mfbz.cn/a/588621.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WebGL/Cesium 大空间相机抖动 RTE(Relative to Eye)实现原理简析

在浏览器中渲染大尺寸 3D 模型&#xff1a;Speckle 处理空间抖动的方法 WebGL/Cesium 大空间相机抖动 RTE(Relative to Eye)实现原理简析 注: 相机空间和视图空间 概念等效混用 1、实现的关键代码 const material new THREE.RawShaderMaterial({uniforms: {cameraPostion: {…

【Qt QML】用CMake管理Qt工程

CMake是一个开源、跨平台的工具系列&#xff0c;用于构建、测试和打包软件。CMake使用简单的独立配置文件来控制软件编译过程。与许多跨平台系统不同&#xff0c;CMake被设计为与本地构建环境结合使用。 下面我们在CMake项目中使用Qt的最基本方法。首先&#xff0c;创建一个基本…

如何解决pycharm创建项目报错 Error occurred when installing package ‘requests‘. Details.

&#x1f42f; 如何解决PyCharm创建项目时的包安装错误&#xff1a;‘requests’ &#x1f6e0;️ 文章目录 &#x1f42f; 如何解决PyCharm创建项目时的包安装错误&#xff1a;requests &#x1f6e0;️摘要引言正文&#x1f4d8; **问题分析**&#x1f680; **更换Python版本…

OpenCV 实现重新映射(53)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV 实现霍夫圆变换(52) 下一篇 :OpenCV实现仿射变换(54) 目标 在本教程中&#xff0c;您将学习如何&#xff1a; 一个。使用 OpenCV 函数 cv&#xff1a;&#xff1a;remap 实现简…

Java Web 开发 - 掌握拦截器和监听器

目录 深入了解Java Web的拦截器和监听器 拦截器&#xff08;Interceptor&#xff09; 拦截器的使用场景 拦截器实例 思维导图 ​编辑 监听器&#xff08;Listener&#xff09; 监听器的使用场景 监听器类型 监听器实例 思维导图​编辑 总结 深入了解Java Web的拦截器…

C——双向链表

一.链表的概念及结构 链表是一种物理存储单元上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。什么意思呢&#xff1f;意思就是链表在物理结构上不一定是连续的&#xff0c;但在逻辑结构上一定是连续的。链表是由一个一个的节点连…

uniapp0基础编写安卓原生插件和调用第三方jar包和编写语音播报插件之使用jar包插件

前言 如果你不会编写安卓插件,你可以先看看我之前零基础的文章(uniapp0基础编写安卓原生插件和调用第三方jar包和编写语音播报插件之零基础编写安卓插件), 我们使用第三方包,jar包编写安卓插件 开始 把依赖包,放到某个模块的/libs目录(myTestPlug/libs) 还要到build…

java-函数式编程-函数对象

定义 什么是合格的函数&#xff1f;无论多少次执行函数&#xff0c;只要输入一样&#xff0c;输出就不会改变 对象方法的简写 其实在类中&#xff0c;我们很多参数中都有一个this&#xff0c;被隐藏传入了 函数也可以作为对象传递&#xff0c;lambda就是很好的例子 函数式接口中…

ROS实操:通信机制的实现

最近闲来无事&#xff0c;打算重温了一下ROS方面的相关知识。先前的学习都是一带而过&#xff0c;发现差不多都忘了&#xff0c;学习的不够深入。因此&#xff0c;在重温的同时&#xff0c;写下了这篇关于ROS架构的学习博客。 上一篇博客的链接为&#xff1a;ROS架构的学习【No…

如何利用有限的数据发表更多的SCI论文?——利用ArcGIS探究环境和生态因子对水体、土壤和大气污染物的影响

原文链接&#xff1a;如何利用有限的数据发表更多的SCI论文&#xff1f;——利用ArcGIS探究环境和生态因子对水体、土壤和大气污染物的影响https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247602528&idx6&snc89e862270fe54239aa4f796af07fb71&chksmfa82…

visio画图基本用法

添加图形 点击上面的箭头 添加一些基本的形状 添加连接点 点击这个 X 按住Ctrl&#xff0c;在想要的位置上添加连接点 更改线条样式 选中线条之后&#xff0c;右键 可以选择箭头样式 添加文本框 visio对象复制到word里面&#xff0c;画布存在大量空白问题 https://blog.…

【C语言】深入了解文件:简明指南

&#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专栏&#xff1a; C笔记 &#x1f308;喜欢的诗句:无人扶我青云志 我自踏雪至山巅 文章目录 一、文件的概念1.1 文件名:1.2 程序文件和数据文件 二、数据文…

如何利用MCU自动测量单元提高大坝安全监测效率

大坝作为重要的水利基础设施&#xff0c;其安全性直接关系到人民群众的生命财产安全和社会的稳定发展。因此&#xff0c;对大坝进行实时、准确的安全监测至关重要。近年来&#xff0c;随着微控制器单元(MCU)技术的不断发展&#xff0c;其在大坝安全监测领域的应用也越来越广泛。…

数据结构——排序算法分析与总结

一、插入排序 1、直接插入排序 核心思想&#xff1a;把后一个数插入到前面的有序区间&#xff0c;使得整体有序 思路&#xff1a;先取出数组中第一个值&#xff0c;然后再用tmp逐渐取出数组后面的值&#xff0c;与前面的值进行比较&#xff0c;假如我们进行的是升序排序&…

操作系统的运行机制详解

操作系统的 运行机制 #mermaid-svg-jVBbLUJa6gITOo7L {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-jVBbLUJa6gITOo7L .error-icon{fill:#552222;}#mermaid-svg-jVBbLUJa6gITOo7L .error-text{fill:#552222;stroke…

C 深入指针(1)

目录 一、const 1、const修饰变量 2、const修饰指针 2.1 const int* p&#xff08;int const* p&#xff09; 2.2 int* const p 2.3 结论 二、指针运算 1、指针 - 整数 2、指针 - 指针 3、指针的关系运算 三、指针的使用 1、模拟实现 strlen 2、传值调用和传址调用…

安装VMware Tools报错处理(SP1)

一、添加共享文件 因为没有VMware Tools&#xff0c;所以补丁只能通过共享文件夹进行传输了。直接在虚拟机的浏览器下载的话&#xff0c;自带的IE浏览器太老了&#xff0c;网站打不开&#xff0c;共享文件夹会方便一点&#xff0c;大家也可以用自己的方法&#xff0c;能顺利上…

Kafka介绍、安装以及操作

Kafka消息中间件 1.Kafka介绍 1.1 What is Kafka&#xff1f; 官网&#xff1a; https://kafka.apache.org/超过 80% 的财富 100 强公司信任并使用 Kafka &#xff1b;Apache Kafka 是一个开源分布式事件流平台&#xff0c;被数千家公司用于高性能数据管道、流分析、数据集成…

kubernetes中使用ELK进行日志收集

目录 一、需要收集哪些日志 1、kubernetes集群的系统组件日志 2、应用日志 二、日志收集方案ELK 1、收集日志&#xff1a;Logstash 2、存储日志&#xff1a;Elasticsearch 3、展示日志&#xff1a;Kibana 三、安装elk 1、下载安装包 2、创建用户并切换到新用户 3、上…

【Excel】excel连接数字和符号

使用“&”对数字和符号进行连接 示例&#xff1a; 将“2.6”和“&#xff0c;”连成“2.6&#xff0c;” 连接公式为&#xff1a; V3&W3 V3和W3分别是"2.6"和“&#xff0c;”在excel中的位置
最新文章