首页 > 其他分享 >B+树要点梳理

B+树要点梳理

时间:2024-07-15 11:52:28浏览次数:8  
标签:upper 中间 节点 相邻 key 要点 梳理 指针

B+树重要操作

中间节点

  • 中间节点的key,与其对应的指针的原则是,小于key的元素在其指针指向的节点中
  • 中间节点的key可以看成是右斜着排放的,即小于等于key的节点由key对应的指针指定,最有一个指针指向大于最右侧key的节点

分裂

  • 当中间节点数量满了时,进行分裂,新生成一个相邻的中间节点right_internal_node
  • 计算原先保留多少元素, 例如保留left_count = K/2
  • 将left_count对应的key作为upper_key后续插入到中间节点
  • 从left_count + 1下标开始,将key复制到新生成的节点中
  • 将left_count下表对应的指针留在左节点
    • 将left_count +1 下表对应的指针开始复制到新生成的节点中

分裂后插入上层中间节点

  • 经过分裂有三个重要元素:left_internal_node(也就是原先被分裂节点), right_internal_node(新生成的节点),upper_key
  • 上层中间节点为upper_internal_node
  • 注意原先upper_internal_node有一个old_key和old_ptr指针指向left_internal_node,这个old_key肯定是比upper_key大的
    • 将(upper_key, left_internal_node)作为一对插入到upper_internal_node
    • 将old_key对应的old_ptr改成指向right_internal_node

image

删除

借入数据

中间节点删除数据后,如果数量少于最低值K/2,那么它可以尝试从它的相邻节点借入数据。那么它的相邻节点在哪里呢?第一类相邻节点是与中间节点共享一个父节点的节点,很容易通过其父节点找到。第二类相邻节点,不与其共享一个父节点:想象一下当节点是父节点的最左边节点时,那么在其父节点下,它只有一个右侧相邻节点,这个时候它的左侧相邻节点不在其父节点下;当节点是父节点的最右边节点时,那么在其父节点下,它只有一个左侧相邻节点,这时候它的右侧相邻节点不在其父节点下,而在右侧树分支下。

image

  • 注意第二类的借入在实现的时候也可以不考虑,只不过这样会过早的带来节点合并。

节点合并

当节点确实从左右都没有节点可以借入数据时,那么它就可以与左右节点合并了。正因为其左右节点也没有多的节点可以借,说明其左右节点不大于K/2,那么合并后节点节点数量是会小于K的。

节点合并,我们可以总是选择与其相同父节点下的相邻节点进行合并。节点合并后,需要使父节点删除掉被合并数据的节点(需要注意的是在实际操作中,我们可以选择被删除的节点最终保留下来,也可以选择相邻节点最终保留下来,这只是实现方式)。

降级

父节点删除某个子节点后,需要检查父节点的key数量是否满足K/2要求,如果父节点不满足,那么父节点也需要执行借入动作,如果父节点无法接入,那么就需要执行合并动作。并最终出发祖父节点的删除动作。整个流程会递归上去最终可能会到根节点,若根节点删除了数据,且一个key也没有了。那么就要将其子节点设置成新的root了(奇怪?没有key还有子节点?注意中间节点指针比key多一个,就是说没有key,也至少有一个指针)。

叶子节点

插入

分裂

  • 当叶子节点数量满了时,进行分裂,新生成一个相邻sibling节点
  • 计算原先保留多少元素, 例如保留left_count = K/2
  • 将下标left_count开始的key移动到新生成的节点
    • 将left_count对应的key作为upper_key后续插入到中间节点
  • 将下标left_count开始的value移动到新生成的节点

分裂后插入上层中间节点

  • 新分裂出的节点的第一个key为upper_key
  • 如果叶子节点没有上层中间节点,那么就新建一个中间节点作为根节点,把原先叶子节点以及新分裂出的叶子节点直接插入到新的中间节点。同时将upper_key插入到新中间节点中。即它有1个key,两个指针。
  • 如果有上层中间节点
    • 首先将upper_key插入到中间节点
    • 其次将新分裂出的节点指针也插入到中间节点,这里需要注意的是由于中间节点的指针数量比key数量多一个。所以指针插入的index要比upper_key插入的index大1
      image

删除

  • 删除后,尝试从其右侧的节点借数据,前提是右侧节点包含m/2 +1个元素
  • 若节点(且该节点不是中间节点的最左侧节点)最左边一个元素被删除了,则需要更新中间对应的key

借入数据

借入数据的流程是简单的,与中间节点类似。找到其相邻节点并拿到数据。同样需要注意的是它的相邻节点也可能不在其父节点下。

节点合并

与中间节点的操作类似,找到相邻节点,拷贝数据。通知父节点删除一个节点即可。

要记录哪些指针

  • 从B+树的定义上来看,至少每个叶子节点需要记录一个指向右侧相邻节点的指针

其他指针要不要记录呢?

  • 节点是否要记录父节点指针?
  • 中间节点是否要记录右侧节点的指针?
  • 节点是否要记录其左侧节点的指针?

这个因设计实现而异了,记录更多的指针,那么在寻找定位的时候肯定更加容易,但是也带来了很多维护成本,这个在设计的时候做考量。

如何找到相邻的节点

相同父节点下的相邻节点

很容易,很自然,进行遍历就可以或者对key进行find或二分法(注意所有节点里面的key都是有序的)

非相同父节点下的相邻节点

  • 最节点的方式是记录节点(包括中间节点)的左侧、右侧指针

另外一种方式,如果不想过多的记录指针,那么可以通过搜索回溯的方式来找目标节点到相邻节点。 某个最左侧中间节点为例:

  • 从它开始往上回溯,当回溯到的节点不是其父节点的最左侧节点时,说明我们已经找到了共同祖先
  • 然后从共同祖先中的左侧节点(对应下图1的左侧节点3)开始往下找其最右侧的子节点,当往下搜索的层数与向上回溯的层数相同时,我们就找到了目标节点的相邻节点。
  • 最右侧中间节点的搜索过程是类似的。
    image

例如想找到节点5的做相邻节点4,就往上回溯首先到1, 5是1的最左侧节点,则继续向上到2,1不是2的最左侧节点,所有2就是共同祖先了。1的左侧节点3往下,找到最右侧节点4, 4与5是相同层级的,那么就是5的相邻左节点了。

另外一个问题是如何进行回溯呢?一种实现方式是子节点记录父节点的指针;另一种实现方式是,记录搜索过程的搜索栈(即从根节点到叶子节点经过了哪些中间节点,记录到一个列表里面)。

标签:upper,中间,节点,相邻,key,要点,梳理,指针
From: https://www.cnblogs.com/bymzy/p/18302898

相关文章

  • C++11标准库 未来体 <future> 梳理
    目录<future>future模板类成员函数:promise类promise的使用例程:packaged_task模板类例程:async模板函数例程:shared_future模板类<future>标准库提供了一些工具来获取异步任务(即在单独的线程中启动的函数)的返回值,并捕捉其所抛出的异常。这些值在共享状态中传递,其中异步任务可以......
  • C++11标准库 条件变量 <condition_variable> 梳理
    目录<condition_variable>condition_variable类类方法生产者消费者模型--阻塞队列单条件变量版condition_variable_any模板类区别优缺点<condition_variable>条件变量是C++11提供的另外一种用于等待的同步机制,它能阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时......
  • C++11标准库 互斥锁 <mutex> 梳理
    目录<mutex>std::call_once函数例程:使用call_once实现的单例模式std::mutex类--独占互斥锁成员函数std::recursive_mutex类--递归互斥锁使用注意:描述:std::timed_mutex类--超时互斥锁描述:成员函数:std::recursive_timed_mutex类std::lock_guard模板类函数原型:std::uniqu......
  • C++11 标准库 线程库<thread>梳理
    目录<thread>this_thread命名空间1.get_id()2.sleep_for()3.sleep_until()4.yield()thread类构造函数:类方法1.get_id()2.join()3.detach()4.joinable()5.operator=6.hardware_concurrency(static)多线程的两种计算场景<thread>this_thread命名空间在C++11中不仅添加......
  • C++11时间工具<chrono>梳理
    目录<chrono>时间间隔duration常用的duration时间点time_point时钟system_clock&steady_clocksystem_clock代码举例steady_clock例程:转换函数1.duration_castDescription:duration支持隐式转换的规则2.time_point_cast<chrono>C++11中提供了日期和时间相关的库chrono。chro......
  • ROS学习笔记总结篇(基础篇梳理)
    在学习完一到十章节的ros后,我们的ros基础篇也迎来了结束,因此,在这章节,我会做一个总结,将一到十章的内容之串起来,实操一遍,接下来我们直接进入实操!一、创建一个工作空间首先,按下ctrl+shift+T打开终端,创建一个新的ros工作空间。#创建工作空间目录mkdir-psyj_ws/src#进入syj_......
  • 十万字梳理,带你拿下Java面试题(含答案解析)!
    马上金九银十很多同学/朋友都在忙着找工作,小五给大家整理了一份非常全面的Java面试题含答案总结!从java面试新手到java面试专家,你只差这一份面试题!还不赶紧拿下~内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、SpringBoot、Spri......
  • Franka Robot 相关ROS1节点梳理
    自底向上:1.franka_hw_node:这是Franka机器人的核心节点,负责与底层硬件进行交互。它会从 libfranka 库获取机器人的状态信息,如关节角度、关节力矩等。它会发布这些状态信息到对应的ROS话题上,供其他节点使用。2.joint_state_publisher:这个节点会订阅 franka_h......
  • 面经梳理-分布式
    题目请简述一下CAP理论,我们常见的中间件分别侧重点是什么?简述一下BASE理论?什么是强一致性,弱一致性,最终一致性CAP理论CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partitiontolerance(分区容错性)这三个基本需求,最多只能同时满足其中的2......
  • 面经梳理-spring
    题目聊聊spring的IOC和AOP?其底层原理分别是什么?IOC没有使用spring,所有的对象均需要手动创建,相当于底层类控制了上层类,例如轮子类控制了汽车类,创建汽车对象的时候需要传入轮子的参数。IOC意思就是控制反转,即上层类控制下层类,上层类的对象只需要指定需要什么类型的下层类对象,由......