B树

news2024/11/17 13:44:07

文章目录

  • B树的定义和性质
    • 为什么需要B树
    • B树的定义
  • B树的模拟实现
    • 节点的数据结构
    • B树的插入
    • B树的删除
  • B树的模拟实现

B树的定义和性质

我们之前已经对 平衡搜索二叉树有了一定的了解,学习了两种树——AVL树 和 红黑树,下面介绍一下B树

为什么需要B树

数据库中使用的就是B+树(和B树原理是一样的后面会单独介绍),那我们为什么不使用的AVL树和红黑树作为数据库的索引呢 ?原因是每次查询都是一次磁盘IO(数据库中的数据是存在file system中,每次查询的时候是需要读到内存中的),而磁盘IO中耗时最长的就是将磁盘读写磁头的定位,而每次查询都大概率都会移动磁头。所以如何降低IO的次数(实际上就是降低树的高度),就是降低总体查询时间。B树就是再次基础上做出了两点改进:

  • 增加了没给节点的关键字数量——原先每个节点都是存储一个关键字,B树的每个节点存储M个关键字,这些关键字始终维护成一个有序的递增数列
  • 把二叉树改成多叉树
    这样一个树的高度就被压缩了很多,单次查询某个关键字所需要io的次数也变少了

B树的定义

首先我们要了解一下B树的度:即每个B树节点最多可以有M个子节点,这个M就是B树的度

- 每个结点的值(索引) 都是按递增次序排列存放的,并遵循左小右大原则。
- 对于所有节点来说:
           关键字的数量始终=子节点的数量-1(这一点不论B树什么时候都必须满足)
           - 对于根节点:
                -关键字的数量满足[1,M-1]
                - 子节点的数量满足[2,M]   
           - 对于除了根节点以外的其他节点
                -关键字的数量满足[Math.ceil(M/2) , M-1]   //Math.ceil代表向上取整
                - 子节点的数量满足[Math.ceil(M/2) , M]     //Math.ceil代表向上取整
- B树所有叶子节点位于同一层

我们下面都以度为3的B树作为样例作为讨论:

那么度为3的B树的节点吗,满足什么特性呢?

  • 除了根节点以外的其他节点满足:
    • 关键字个数:[1,2]
    • 子节点的个数:[2,3]
  • 根节点:
    • 关键字的个数:[1,2]
    • 子节点的个数:[2,3]

对于度为3的B树根节点和非根节点的要求是一样的,但是当度M>=5时候就会不一样了,读者可以自己计算一下。

B树的模拟实现

以下所有的都是以度为3的B树为示例
然后我们用less_key_number表示一个节点关键字最少的数量,这里为ceil(3/2)-1=1
注意:less+key_number的数量始终小于M/2——这里M/2不取整

节点的数据结构

节点数据结构的伪代码:

int cur_sz=0;   //记录当前key的数量
K array_k[M];   //Key的数组
BTreeNode<K, M>* array_node[M+1]; // 存储子节点的数组

这里我们把关键字数组和子节点数组都多开了一个值,实际上还是符合上面B树的规则,只是方便后面插入时候将插入和调整过程分离
在这里插入图片描述
注意

  • 我们把与array_k下标相等array_node叫做关键字array_k的左节点
  • 我们把比array_k下标大一的array_node叫做关键字array_k的右节点

例如下标为i的array_k[i]的左节点为:array_node[i]右节点为:array_node[i-1]

B树的插入

插入过程的思路
在这里插入图片描述
注意: 我们在插入之前B树的所有节点的关键字都是满足数量的要求的,这是插入的前提

  • 假设我们现在有一个节点如下:现在我们想要插入关键字9
    在这里插入图片描述

  • 按照上面的思路先插入
    在这里插入图片描述

  • 判断发现不满足B树的节点的关键字数量这时候我们就要进行分裂操作:

    • 首先把该节点分成三部分,三部分关键字的数量为:less_key_number+1+M-1-less_key_number ,(这三部分我们后面分别用部分1、部分2、部分3代称)如果大伙感兴趣可以计算一下上面三个部分每个部分的关键字数量都不超过M/2(不论M是奇数还是偶数)。
    • 把部分1和部分3分别分裂成两个新节点,部分2 作为关键字插入该节点的父节点
      注意
      • 部分1和部分3分家的时候,我们把原节点当做部分1,创立一个新节点当做部分3,新节点部分三的array_nodearray_k都要从部分1中拷贝
      • 部分2插入父节点,首先需要找到部分二在array_k数组中的插入下标,而新分裂出来的两个子节点(部分1和部分3)就是部分2在父节点插入位置的左右子节点
      • 父节点插入部分2的时候涉及到数组的移动,注意array_karray_node两个数组都需要相应的移动(这里的例子看不出来,下面会举一个相应的例子)
      • 如果像这个例子一样没有父节点或者说该节点就是根节点,那么需要创立一个父节点节点,并更新根节点
        在这里插入图片描述
  • 因为父节点中插入了一个新的关键字,可能导致父节点的关键字数量超出M-1所以需要继续向上遍历判断

ok这个最简单的例子看完了我们来看一个复杂一点的例子:
假设我们现在有了一个如下的B树👇,现在我们要插入关键字:3
在这里插入图片描述

  • 首先找到关键字3应该在哪个节点插入——很简单按照二叉搜索树去找(这里就不细说了)

  • 找到后直接插入
    在这里插入图片描述

  • 发现条件不满足——分裂
    在这里插入图片描述

  • 调整后

  • 此时父节点已经不满足B树的规则继续调整
    在这里插入图片描述

  • 在进行分裂
    在这里插入图片描述

注意
上文在介绍树节点的数据结构的时候不论是array_k还是array_node的时候都比B树规定的多开了一个空间,到这里你应该能体会到为什么了吧?
目的就是为了将插入和分裂两个过程解耦,不然插入和分裂弄在一块就会很麻烦

B树的删除

B树的删除思路如下

在这里插入图片描述
下面我将举几个例子来带大家深入对删除规则的理解:
以下面👇这个B树为例:通过删除不同的关键字来将删除思路中的不同情况加以说明
在这里插入图片描述

  • 情况一
    对下面这个B树删除关键字33
    在这里插入图片描述

    • 首先先要删除关键字所在的叶子节点——然后直接删除(注意这里的删除需要调整array_key数组)
    • 删除之后,发现该节点的关键字数量依然>=less_key_number,所以不用调整
      最后的删除结果如下
      在这里插入图片描述
  • 情况二
    还是删除这个B树👇删除关键字53
    在这里插入图片描述

    • 所有删除的前面两步都是相同的——找到要删除关键字所在的节点,直接删除该关键字
    • 删除53关键字之后,该节点的关键字个数<less_key_number需要调整
    • 这时优先向兄弟节点借关键字(这里还有一个潜台词:兄弟节点的关键字数量一定是>=less_key_number),看看兄弟节点是否有多余的关键字(多余指的是:关键字数量>less_key_number),然后我们发现很好有一个兄弟节点bro多出了一个关键字
      在这里插入图片描述
    • 最后就是移动节点环节,由于移动的过程中要保持是一个B树,所以bro节点和删除节点中间的子节点都需要移动,同时不仅需要移动array_node数组还需要移动array_k数组
      在这里插入图片描述
      最终结果就是:
      在这里插入图片描述
  • 情况三
    对下面这个B树删除关键字49
    在这里插入图片描述

    • 和前面一样前两步依然一样:找到关键字所在节点,删除关键字

    • 删除完了之后,我们发现不满足关键字数量>=less_key_number于是我们优先看看兄弟节点是否有多余的关键字

    • 找了一圈发现兄弟也没有多余的(注意这里的情况有一个潜台词:所有兄弟节点的关键字数量等于less_key_number,如果兄弟节点关键字数量大于less_key_number一定可以借给删除节点,如果小于less_key_number则不符合B树的定义),这时只能向爸爸(父节点)借🤣,由于父节点被接走了一个关键字此时还必须满足关键字数量==子节点数量-1,所以删除节点还需要和一个兄弟节点合并。
      我们现在来计算一下合并后的新节点的关键字数量:less_key_number-1(删除节点的关键字数量)+ 1(从父节点借的关键字)+less_key_number(兄弟节点的关键字数量:上面论证过为什么一定等于这个数)=2*less_key_number。而上文我们已经知道less_key_number<M/2(不取整),所以2*less_key_number一定小于等于M-1
      注意:在合并的过程中array_k数组和array_node数组两个都需要调整,你叶子节点调不调整看不出来,但是中间节点不调整就会出问题
      在这里插入图片描述
      合并之后:
      在这里插入图片描述
      注意虽然这里的cur节点一个关键字没有,他依然满足关键字数量==子节点数量-1

    • 这时我们把cur节点当做被删除节点,重复上述步骤
      在这里插入图片描述
      最终得到:
      在这里插入图片描述

  • 情况四

  • 上面删除的关键字无一例外的都是叶子节节点的关键字,如果删除的关键字不在叶子节点怎么办?这个思路也很简单——找删除关键字所在节点左子树的最大值 或 右子树的最小值 替换,替换完之后又变成了叶子节点删除。这个在红黑树和AVL树中都有体现就不细说了

B树的模拟实现

模拟实现了一下,通过了自己编的一些测试用例,不保证对🤗
代码

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

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

相关文章

Nacos 服务网格⽣态

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战、定制、远程&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面…

为一加七Pro(LineageOs17.1 4.14内核版本)编译KernelSu

编译内核 因为一加七的内核版本是4.14&#xff0c;所以想使用kernelsu&#xff0c;需要自己将kernelsu编译到内核里。 我使用的系统是&#xff1a;LineageOS17.1&#xff0c;对于之后的lineage版本同样适用&#xff0c;只是拉取的源代码不一样。刷机教程请看&#xff1a;wsl2…

vue diff算法与虚拟dom知识整理(3) 了解h函数和虚拟节点概念 实现虚拟节点上dom树

虚拟dom之前我们也有了基本的了解 简单说 就是用js数据结构来描述html的dom结构树 首先 为什么要用虚拟dom啊&#xff1f; 官方给出的回答是 diff最小量精细化算法是发生在虚拟dom上的 也就是 我们之前说的 节点与节点比较 并不是发生在html元素上的 而是发生在js中的虚拟dom上…

【C++学习】类和对象--多态【待补充】

多态的基本语法 多态是C面向对象三大特性之一 静态多态&#xff1a; 函数重载和运算符重载属于静态多态&#xff0c;复用函数名&#xff08;函数地址早绑定&#xff0c;编译阶段确定函数地址&#xff09; 动态多态&#xff1a; 派生类和虚函数实现运行时多态&#xff08;函数地…

centos7.6 yum 安装mysql

目录 1. 删 mariadb / 自带MySQL 2 安装wget命名 3 下载并安装MySQL官方的 Yum Repository 4 使用yum安装mysql 5 启动 6 获取密码 7 登录 -> 没有获取到 就直接按回车 不输入 8 设置密码 及权限 --> root 账号所有语句报错 9 参考 1. 删 mariadb / 自带MySQL…

《计算机网络——自顶向下方法》精炼——2.7.2(TCP套接字编程)

“学习的敌人是自己的满足。” —— 叶圣陶 文章目录 TCP套接字编程TCP套接字编程概述客户进程服务器进程 TCP套接字编程 TCP套接字编程概述 TCP是一个面向连接的运输层协议&#xff0c;因此可以分为发起连接的阶段和传输阶段。 发起连接时&#xff0c;客户进程创建一个客户…

【LeetCode】221.最大正方形

221.最大正方形&#xff08;中等&#xff09; 题解 对于在矩阵内搜索正方形或长方形的题型&#xff0c;一种常见的做法是&#xff1a;定义一个二维 dp 数组&#xff0c;其中 dp[i][j] 表示满足题目条件的、以&#xff08;i,j&#xff09;为右下角的正方形或长方形属性。在本题中…

【备战蓝桥杯国赛-国赛真题】费用报销

题目链接&#xff1a;https://www.dotcpp.com/oj/problem2696.html 思路 读完题&#xff0c;再看一眼数据范围&#xff0c;这道题的做法也就确定了——DP。 DP的题目往往很容易辨识出来&#xff0c;所以我们就往DP上想了&#xff0c;第一要素是选出的所有票据里面&#xff0c…

【LeetCode】64. 最小路径和

64. 最小路径和&#xff08;中等&#xff09; 方法一&#xff1a;常规动态规划 思路 定义一个二维 dp 数组&#xff0c;其中 dp[i][j]表示从左上角开始到&#xff08;i, j&#xff09;位置的最优路径的数字和。因为每次都只能向下或者向右移动&#xff0c;所以很容易发现 dp数组…

汽车行业V模型开发详解

在新能源汽车开发过程中&#xff0c;通常会采用V模型&#xff08;V-Model&#xff09;进行系统开发。V模型是一种基于需求分析、体系架构设计、硬件和软件开发、集成测试以及产品验证的系统工程方法。 下面简要介绍新能源汽车V模型开发的主要阶段&#xff1a; V模型开发&…

encrypted勒索病毒攻击nas服务器,服务器中了勒索病毒解密数据恢复

近年来&#xff0c;勒索病毒的攻击技术不断升级&#xff0c;各种加密型的病毒不断出现&#xff0c;给我们工作和生活带来了很大困扰。其中&#xff0c;encrypted勒索病毒攻击NAS网络存储设备已经变得越来越常见。而这次我们将为大家探讨如何预防encrypted勒索病毒攻击NAS服务器…

springboot+vue教师人事档案管理系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的教师人事档案管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1…

架构集群部署

这是一个简单的集群框架 192.168.142.10做负载均衡&#xff08;主&#xff09; 192.168.142.20&#xff08;副&#xff09; 先做keepalive 两台nginx做七层反向代理&#xff08;动静分离&#xff09; 192.168.142.30 192.168.142.40 部署tomcat做多实例部署 192.168.14…

linux驱动开发 - 11_Linux 下的驱动分离与分层

文章目录 11. Linux 下的驱动分离与分层1 驱动的分隔与分离2 驱动的分层 11. Linux 下的驱动分离与分层 1 驱动的分隔与分离 linux是一个成熟、复杂、庞大的操作系统&#xff0c;代码的重用性很重要&#xff0c;不然会在linux内核存在大量的无意义重复的代码。尤其的驱动程序…

进阶Spring(2)-BeanFactory和ApplicationContext实现

&#x1f3e0;个人主页&#xff1a;阿杰的博客 &#x1f4aa;个人简介&#xff1a;大家好&#xff0c;我是阿杰&#xff0c;一个正在努力让自己变得更好的男人&#x1f468; 目前状况&#x1f389;&#xff1a;24届毕业生&#xff0c;奋斗在找实习的路上&#x1f31f; &#x1…

[Data structure]单链表常见算法题

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;数据结构。数据结构专栏主要是在讲解原理的基础上拿Java实现 ⭐如果觉得文章写的不错&#xff0c;欢迎点个关注一…

Hibernate(二)——Springboot整合Hibernate

在了解了Hibernate后&#xff0c;进一步将Hibernate和Springboot整合。 目录 引入依赖配置文件代码BeanDao层Service层Controller层 测试JpaRepository接口 引入依赖 <!--引入hibernate--> <dependency><groupId>org.springframework.boot</groupId>…

【大数据处理与可视化】四、数据预处理

【大数据处理与可视化】四、数据预处理 实验目的实验内容实验步骤一、案例——预处理部分地区信息1、导包2、读取文件3、检查并删除重复数据北京天津&#xff08;无重复数据&#xff09; 4、检查缺失值北京&#xff08;无缺失值&#xff09;天津&#xff08;向前填充&#xff0…

10分钟学会搭建sovits第一篇

So-vits-svc 基于端到端架构的VITS和soft-vc&#xff0c;用户只需准备几十分钟到几个小时不等的语音或歌声数据&#xff0c;就能制作&#xff08;训练&#xff09;属于自己的 AI 声库 &#xff08;前提是你的显卡足够给力&#xff09;&#xff0c;将一段语音或歌声转换为你想要…

简易时钟-QT学习

1 .h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPaintEvent> //绘制事件 #include <QPainter> //画家类 #include <QPaintDevice> #include <cmath> // #include <QPainterPath> #include <QTime> //时间类…