[C++][数据结构][B-树][下]详细讲解

目录

  • 1.B-树的实现
    • 1.B-树的结点设计
    • 2.插入key的过程
    • 3.B-树的插入实现
    • 4.B-树的简单验证
    • 5.B-树的性能分析
    • 6.B树的删除
  • 2.B+树
  • 3.B*树
  • 4.B-树总结
  • 5.B-树的应用
    • 0.B树可以在内存中做内查找吗?
    • 1.索引
    • 2.MYSQL索引简介
      • 1.MyISAM
      • 2.InnoDB
    • 3.B+树做主键索引相比B树的优势


1.B-树的实现

1.B-树的结点设计

template<class K, size_t M>
struct BTreeNode
{// 为了方便插入以后再分裂,多给一个空间K _keys[M];BTreeNode<K, M>* _subs[M + 1];     BTreeNode<K, M>* _parent = nullptr;;size_t _n = 0; // 记录实际存储关键字的个数BTreeNode(){for (size_t i = 0; i < M; i++){_keys[i] = K();_subs[i] = nullptr;}_subs[M] = nullptr;}
};

2.插入key的过程

  • 按照插入排序的思想插入key
    • 注意:在插入key的同时,可能还要插入新分裂出来的节点
void InsertKey(Node* node, const K& key, Node* child)
{// 直接插入排序int end = node->_n - 1;while (end >= 0){if (key < node->_keys[end]){node->_keys[end + 1] = node->_keys[end];node->_subs[end + 2] = node->_subs[end + 1];end--;}else{break;}}node->_keys[end + 1] = key;node->_subs[end + 2] = child; // 该结点的右子树if (child){child->_parent = node;}node++;
}

3.B-树的插入实现

bool Insert(const K& key)
{if (_root == nullptr){_root = new Node;_root->_keys[0] = key;_root->_n++;return true;}// key已存在,不允许插入pair<Node*, int> ret = Find(key);if (ret.second >= 0){return false;}// 如果没有找到,Find()顺便带回了要插入的那个叶子结点// 循环每次往cur插入,newkey和childNode* parent = ret.first;K newKey = key;Node* child = nullptr;while (1){InsertKey(parent, newKey, child);// 没有满,插入就结束if (parent->_n < M){return true;}// 满了就要分裂// 分裂一般[mid + 1, M - 1]给兄弟Node* bro = new Node;size_t mid = M / 2;size_t i = mid + 1;size_t j = 0;for (; i < M; i++){// 分裂拷贝key和key的左孩子bro->_keys[j] = parent->_keys[i];bro->_subs[j++] = parent->_subs[i];// 更新parent->_keys[i]父节点为broif (parent->_keys[i]){parent->_keys[i]->_parent = bro;}// 拷走之后,重置为默认值,方便观察parent->_keys[i] = K();parent->_subs[i] = nullptr;}// 还有最后一个最右孩子bro->_subs[j] = parent->_subs[i];if (parent->_keys[i]){parent->_keys[i]->_parent = bro;}parent->_subs[i] = nullptr;bro->_n = j;parent->_n -= (bro->_n + 1);K midkey = parent->_keys[mid];parent->_keys = K();// 说明刚刚分裂的是根节点if (parent->_parent == nullptr){_root = new Node;_root->_keys[0] = midkey;_root->_subs[0] = parent;_root->_subs[1] - bro;_root->_n = 1;parent->_parent = _root;bro->_parent = _root;break;}else{// 转换成往parent->_parent中去插入midKey和bronewKey = midkey;child = bro;parent = parent->_parent;}}return true;
}

4.B-树的简单验证

  • 对B树进行中序遍历,如果能得到一个有序的序列,说明插入正确
	void _InOrder(Node* cur){if (cur == nullptr){return;}// 左 根 左 根 ... 右for (size_t i = 0; i < M; i++){_InOrder(cur->_subs[i]); // 左子树cout << cur->_keys[i] << " "; // 根}_InOrder(cur->_subs[i]); // 最右子树}void InOrder(){_InOrder(_root);}

5.B-树的性能分析

  • 对于一棵结点为N,度为M的B-树,查找和插入需要 l o g ( M − 1 ) N log_{(M-1)}N log(M1)N~ l o g ( M / 2 ) N log_{(M/2)}N log(M/2)N次比较
    • 对于度为M的B-树,每一个节点的子节点个数为 M / 2 − ( M − 1 ) M/2 - (M-1) M/2(M1)之间
    • 因此树的高度应该在要 l o g ( M − 1 ) N log_{(M-1)}N log(M1)N l o g ( M / 2 ) N log_{(M/2)}N log(M/2)N之间
    • 在定位到该节点后,再采用二分查找的方式可以很快的定位到该元素
  • B-树的效率是很高的,对于N = 620亿个节点,如果度M为1024,则 l o g ( M / 2 ) N log_{(M/2)}N log(M/2)N <= 4
    • 即:在620亿个元素中,如果这棵树的度为1024,则需要小于4次即可定位到该节点,然后利用二分查找可以快速定位到该元素,大大减少了读取磁盘的次数

6.B树的删除

  • 学习B树的插入足够帮助理解B树的特性了
  • 大致思路:
    • 节点数量小于 m / 2 − 1 m/2-1 m/21,则优先找父亲借,父亲找兄弟借
    • 若找父亲和兄弟借不到节点了,再借它们也不满足条件 m / 2 − 1 m/2 - 1 m/21
      • 合并兄弟节点
  • 若对删除有兴趣,可以参考参考
    • 《算法导论》-- 伪代码
    • 《数据结构-殷人昆》-- C++实现代码

2.B+树

  • B+树是B树的变形,是在B树基础上优化的多路平衡搜索树
  • B+树的规则跟B树基本类似,但又在B树的基础上做了以下几点改进优化:
    • 分支结点的子树指针与关键字个数相同
      • 相当于取消了最左边的那个子树
    • 分支结点的子树指针 p [ i ] p[i] p[i]指向关键字值大小在 ( [ k [ i ] , k [ i + 1 ] ) ([k[i],k[i+1]) ([k[i]k[i+1])区间之间
      • 分支结点跟叶子结点有重复的值,分支结点存的是叶子节点索引
      • 父亲中存的是孩子节点中的最小值的索引
    • 所有叶子节点增加一个链接指针链接在一起
    • 所有关键字及其映射数据都在叶子节点出现
  • B+树的特性:
    • 所有关键字都出现在叶子节点的链表中,且链表中的节点都是有序的
    • 不可能在分支结点中命中
    • 分支节点相当于是叶子节点的索引,叶子节点才是存储数据的数据层
  • B+树的分裂:
    • 第一次插入两层节点,一层做根,一层做分支
      • 后面跟B树一样,往叶子去插入
    • 当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针
    • B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针
  • B+ vs B
    • 分支结点只存储key,分支结点比较小
    • 分支结点映射的磁盘数据块就可以尽量加载到Cache
  • 总结:
    • 简化B树孩子比关键字多一个的规则,变成相等

    • 所有值都在叶子上,方便遍历查找所有值

      请添加图片描述


3.B*树

  • B*树是B+树的变形,在B+树的非根和非叶子节点再增加指向兄弟节点的指针
  • B*树的结点关键字和孩子数量 --> [ 2 / 3 ∗ M , M ] [2/3*M, M] [2/3M,M]
  • B*树的分裂:当一个结点满时
    • 如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了)
    • 如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针
  • 所以,B*树分配新结点的概率比B+树要低,空间使用率更高

请添加图片描述


4.B-树总结

  • **B树:**有序数组+平衡多叉树
  • **B+树:**有序数组链表+平衡多叉树
  • **B*树:**一棵更丰满的,空间利用率更高的B+树

5.B-树的应用

0.B树可以在内存中做内查找吗?

  • 可以,但不合适
  • B树系列和哈希&平衡搜索树对比:
    • 单纯轮树高度、搜索效率而言,B树确实不错
    • 但是B树系列有一些隐形缺点
      • 空间利用率低,消耗高
      • 插入删除数据时,分裂和合并节点,那么必然挪动数据
      • 虽然高度更低,都是在内存中而言,跟哈希和平衡搜索树还是一个量级
  • 结论:实质上B树系列再内存中体现不出优势

1.索引

  • B-树最常见的应用就是用来做索引
  • 索引通俗的说就是为了方便用户快速找到所寻之物,比如:
    • 书籍目录可以让读者快速找到相关信息
    • 网页导航网站,为了让用户能够快速的找到有价值的分类网站,本质上就是互联网页面中的索引结构
  • MySQL官方对索引的定义为:
    • 索引(index)是帮助MySQL高效获取数据的数据结构
    • 简单来说: 索引就是数据结构
  • 当数据量很大时,为了能够方便管理数据,提高数据查询的效率,一般都会选择将数据保存到数 据库,因此数据库不仅仅是帮助用户管理数据,而且数据库系统还维护着满足特定查找算法的数 据结构,这些数据结构以某种方式引用数据,这样就可以在这些数据结构上实现高级查找算法, 该数据结构就是索引

2.MYSQL索引简介

  • MySQL中索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的
  • 注意:索引是基于表的,而不是基于数据库的

1.MyISAM

  • MyISAM引擎是MySQL5.5.8版本之前默认的存储引擎,不支持事物,支持全文检索,使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址,其结构如下:
    请添加图片描述

  • 上图是以Col1为主键,MyISAM的示意图
    - 可以看出MyISAM的索引文件仅保存数据记录的地址
    - 在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复
    - 如果想在Col2上建立一个辅助索引,则此索引的结构如下图所示
    请添加图片描述

  • 同样也是一棵B+Tree,data域保存数据记录的地址

    • 因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引
    • 如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录
    • MyISAM的索引方式也叫做**“非聚集索引”**

2.InnoDB

  • InnoDB存储引擎支持事务,其设计目标主要面向在线事务处理的应用

  • 从MySQL数据库5.5.8版本开始,InnoDB存储引擎是默认的存储引擎

  • InnoDB支持B+树索引、全文索引、哈希索引

    • 但InnoDB使用B+Tree作为索引结构时,具体实现方式却与MyISAM截然不同
  • 第一个区别是InnoDB的数据文件本身就是索引文件

    • MyISAM索引文件和数据文件是分离的, 索引文件仅保存数据记录的地址
    • InnoDB索引,表数据文件本身就是按B+Tree组织的一个索引结构
      • 这棵树的叶节点data域保存了完整的数据记录
      • 这个索引的key是数据表的主键
      • 因此InnoDB表数据文件本身就是主索引
    • 下图是InnoDB主索引(同时也是数据文件)的示意图
      • 可以看到叶节点包含了完整的数据记录,这种索引叫做聚集索引
      • 因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键
        • MyISAM可以没有
      • 如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键
        • 如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键
          • 这个字段长度为6个字节,类型为长整型
            请添加图片描述
  • 第二个区别是InnoDB的辅助索引data域存储相应记录主键的值而不是地址,所有辅助索引都引用主键作为data域
    请添加图片描述

  • 聚集索引这种实现方式使得按主键的搜索十分高效

    • 但是辅助索引搜索需要检索两遍索引
      • 首先检索辅助索引获得主键
      • 然后用主键到主索引中检索获得记录

3.B+树做主键索引相比B树的优势

  • B+树所有值都在叶子,遍历很方便,方便区间查找
  • 队友没有建立索引的字段,全表扫描的遍历很方便
  • 分支结点存储key,一个分支结点占用更小,可以尽可能加载到缓存
  • B树不用到叶子就能找到值,B+树一定要到叶子,这是B树的一个优势
    • 但是B+树高度足够低,所以差别不大

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis-Plus多种批量插入方案对比

背景 六月某日上线了一个日报表任务&#xff0c;因是第一次上线&#xff0c;故需要为历史所有日期都初始化一次报表数据 在执行过程中发现新增特别的慢&#xff1a;插入十万条左右的数据&#xff0c;SQL执行耗费高达三分多钟 因很早就听闻过mybatis-plus的[伪]批量新增的问题&…

BL104应用在智慧零售多协议采集监控远程实时查看

在智慧零售领域&#xff0c;如今的市场竞争日益激烈&#xff0c;传统的零售模式已经难以满足消费者对服务和体验的高需求。智能化技术的引入&#xff0c;尤其是基于物联网的解决方案&#xff0c;成为提升零售业务效率和服务质量的关键。钡铼BL104 Modbus转MQTT网关作为一种先进…

Linux常用命令(17)—pastesortcomm命令(有相关截图)

写在前面&#xff1a; 最近在学习Linux命令&#xff0c;记录一下学习Linux常用命令的过程&#xff0c;方便以后复习。仅供参考&#xff0c;若有不当的地方&#xff0c;恳请指正。如果对你有帮助&#xff0c;欢迎点赞&#xff0c;关注&#xff0c;收藏&#xff0c;评论&#xf…

STM32单片机USART串口打印和收发数据

文章目录 1. 串口通信 1.1 串口初始化 1.2 库函数 2. 串口打印 2.1 Serial.c 2.2 Serial.h 2.3 main.c 3. 串口收发数据 3.1 Serial.c 3.2 Serial.h 3.3 main.c 1. 串口通信 对于串口通信的详细解析可以看下面这篇文章 STM32单片机USART串口详解-CSDN博客 STM32单片…

JDK18特性

JDK18特性 一、JAVA18概述 Java 18 在 2022 年 3 月 22 日正式发布,Java 18 不是一个长期支持版本,这次更新共带来 9 个新功能。 https://openjdk.org/projects/jdk/18/ 二、具体新特性 1. 默认UTF-8字符编码 JDK 一直都是支持 UTF-8 字符编码,这次是把 UTF-8 设置为了默…

基于SpringBoot+Vue在线考试报名系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

c++编译器优化不显示拷贝构造函数

一.错误情景&#xff08;无法打印拷贝函数&#xff09; #include<iostream> using namespace std;class person { public:person(){cout << "person默认构造函数调用" << endl;}person(int age){cout << "有参构造函数调用" <…

网络编程之XDP技术的基础eBPF

一、XDP和TC的技术支撑 在前面分析了XDP和TC技术&#xff0c;从它们的细节里可以看出&#xff0c;它们都在调用eBPF的钩子函数。那么eBPF是什么呢&#xff1f;在2021年曾经写过一篇《eBPF介绍》的初级文章&#xff0c;对eBPF做了一个入门级的普及。但是未曾在技术层面上进行展…

解决双击bootstrap.bat没有生成b2.exe文件

双击bootstrap.bat但是并没有没有生成b2.exe文件&#xff0c;会报如下错误&#xff1a; "cl" 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。D:\cppsoft\boost_1_85_0\tools\build\src\engine>dir *.exe 驱动器 D 中的卷是 Data 卷的序列号是…

【机器学习 复习】第11章 神经网络与深度学习(重中之重)

一、概念 1.神经元模型 &#xff08;1&#xff09;神经网络的基本组成单位 &#xff08;2&#xff09;生物上&#xff0c;每个神经元通过树突接受来自其他被激活神经元的信息&#xff0c;通过轴突释放出来的化学递质改变当前神经元内的电位。当神经元内的电位累计到一个水平时…

广东省建筑施工安管人员考核报名流程及照片处理方法

广东省建筑施工企业安管人员考核工作现已全面启动&#xff0c;这对于提升建筑行业的安全生产管理水平至关重要。为了确保广大考生能够顺利报名并参与考核&#xff0c;本文精心梳理了考核报名流程&#xff0c;并提供了证件照的规范处理方法。同时&#xff0c;针对证件照这一关键…

嵌入式通信协议-----UART协议详解(基于智芯Z20k11X)

目录 一、简介 1.概念 2.结构 3.特点 4.优缺点 二、协议帧组成 1.起始位 2.数据位 3.奇偶校验位 4.停止位 三、UART通信过程 四、USART与UART区别 五、代码实现 1.硬件框图 2.软件实现 一、简介 1.概念 USART&#xff08;Universal Synchronous Asynchronous R…

数字营销新玩法:拓新与裂变的完美结合

在当今这个飞速发展的数字化时代&#xff0c;数字营销已经成为了企业发展中至关重要的一环。拓新&#xff0c;简单来说就是不断去开拓新的客户群体&#xff0c;让更多的人了解并接触到我们的产品或服务。要做到这一点&#xff0c;那可得充分利用各种线上渠道。像热闹非凡的社交…

ffmpeg音视频开发从入门到精通——ffmpeg实现音频抽取

文章目录 FFmpeg 实现音频流抽取1. 包含FFmpeg头文件与命名空间声明2. 主函数与参数处理3. 打开输入文件4. 获取文件信息5. 查找音频流6. 分配输出文件上下文7. 猜测输出文件格式8. 创建新的音频流9. 打开输出文件10. 写入文件头信息11. 读取并写入音频数据12. 写入文件尾部信息…

vue实现的商品列表网页

一、商品列表效果如下 二、代码&#xff1b; vue实现的商品列表网页 &#xff0c; 图片在vue项目的Public文件夹里的 imgs中 <template><div class"common-layout"><!-- el-container:外层容器。 当子元素中包含 <el-header> 或 <el-foo…

如何修复“AI的原罪”

如何修复“AI的原罪” 上个月&#xff0c;《纽约时报》声称&#xff0c;科技巨头OpenAI和谷歌不顾服务条款和版权法的禁止&#xff0c;将大量YouTube视频转录成文本&#xff0c;并将其用作人工智能模型的额外训练数据&#xff0c;从而进入了版权灰色地带。《纽约时报》还援引Me…

细说MCU输出两路PWM波形及改变占空比的实现方法

目录 一、硬件及工程 二、建立工程 三、代码修改 四、下载运行 五、改变PWM波形占空比 1、定义两个全局变量 2、启动定时器 3、重写TIM3中断回调函数 六、下载并运行 一、硬件及工程 文章依赖的硬件及工程配置参考本文作者的其他文章&#xff1a;细说ARM MCU的串口接…

VC++学习(5)——文本编程,插入符的初始化,图形插入符;文字始终在窗口;字符输入功能,回车换行,删除,左键定位;字体修改,字体平滑变色

目录 引出第五讲 文本编程新建项目输入线的初始化根据字体大小定义插入符大小创建图形插入符文字始终保存在窗口中CString类通过字符串资源 路径层字符输入的功能键盘输入消息鼠标左键消息保存点击位置的坐标 输入回车键的处理删除文字的实现 字符输入功能代码字体的修改模拟卡…

开发中遇到的一个bug

遇到的报错信息是这样的&#xff1a; java: Annotation processing is not supported for module cycles. Please ensure that all modules from cycle [hm-api,hm-common,hm-service] are excluded from annotation processing 翻译过来就是存在循环引用的情况&#xff0c;导…

FFmpeg源码:AV_RB32宏定义分析

一、AV_RB32宏定义的作用 AV_RB32是FFmpeg源码中经常出现的一个宏&#xff0c;其定义如下&#xff1a; #ifndef AV_RB32 # define AV_RB32(p) AV_RB(32, p) #endif 该宏定义有多层。把它简化为函数&#xff0c;其函数声明可以等价于&#xff1a; uint32_t AV_RB32(uint…