04 B-树

目录

  1. 常见的搜索结构
  2. B-树概念
  3. B-树的插入分析
  4. B-树的插入实现
  5. B+树和B*树
  6. B-树的应用

1. 常见的搜索结构

种类数据格式时间复杂度
顺序查找无要求O(N)
二分查找有序O( l o g 2 N log_2N log2N)
二分搜索树无要求O(N)
二叉平衡树无要求O( l o g 2 N log_2N log2N)
哈希无要求O(1)

以上结构适合用于数据量相对不是很大,能够一次性存放在内存中,进行数据查找的场景。如果数据量很大,比如有100G数据,无法一次放进内存中,那就只能放在磁盘上了,如果放在磁盘上,有需要搜索某些数据,那么如果处理呢?那么我们可以考虑将存放关键字及其映射的数据的地址放到一个内存中的搜索树的节点中,那么要访问数据时,先取这个地址去磁盘访问数据。

在这里插入图片描述
在这里插入图片描述

使用平衡二叉搜索树的缺陷:
平衡二叉搜索树的高度是logN,这个查找次数在内存中时最快的。但是当数据都在磁盘中时,访问磁盘速度很慢,在数据量很大时,logN次的磁盘访问,是一个难以接受的结果

使用哈希表的缺陷:
哈希表的效率很高是O(1),但是一些极端场景下某个位置冲突很多,导致访问次数剧增

那如何加速对数据的访问?
1.提高IO的速度(SSD相比传统机械硬盘快了不少,但是还没有得到本质性的提升)
2.降低树的高度–多叉平衡树

2. B树概念

1970年,R.Bayer和E.mccreight提出了一种适合外查找的树,它是一种平衡的多叉树,称为B树(后面有一个B的改进版本B+树,然后有些地方的B树写的的是B-树,注意不要误读成"B减树")。一棵m阶(m>2)的B树,是一棵平衡的M路平衡搜索树,可以是空树或者满足一下性质:

1.根节点至少有两个孩子
2.每个分支节点都包含k-1个关键字和k个孩子,其中ceil(m/2) <= k <= m,ceil则是向上取整函数
3.每个叶子结点都包含k-1个关键字,其中ceil(m/2) ≤ k ≤ m
4.所有的叶子节点都在同一层
5.每个节点中的关键字从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域划分
6.每个节点的结构为:{n,A0,K1,A1,K2,A2.。。,Kn,An}其中,K(1≤i≤n)位关键字,且Ki < Ki+1 (1≤i≤n)为指向子树根节点的指针。且Ai所指子树所有节点中的关键字均小于Ki+1
n为节点中关键字的个数,满足ceil(m/2)-1≤n≤m-1

3. B-树的插入分析

为了简单起见,假设M=3,即三叉树,每个节点中存储两个数据,两个数据可以将区间分割成三个部分,因此节点应该有3个孩子,节点结构如下:
在这里插入图片描述
注意:孩子永远比数据多一个
用序列{53,139,75,49,145,36,101}构建B树的过程如下:
在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.1 结构设计

b树的节点,用n记录已经保存的数据数量,用数组保存关键字,孩子数量比关键字多一个,再用一个指针保存当前节点的父亲,方便插入

template <class K, size_t M>
struct BTreeNode
{size_t _n;  // 记录存储多少个关键字K _keys[M]; struct BTreeNode<K, M>* _subs[M + 1];  // 孩子struct BTreeNode<K, M>* _parent;  // 父亲BTreeNode(){for (size_t i = 0; i < M; i++){_keys[i] = K();_subs[i] = nullptr;}_n = 0;_subs[M] = nullptr;_parent = nullptr;}
};

B树保存一个根节点

template <class K, size_t M>
class BTree
{typedef struct BTreeNode<K, M> node;private:node* _root = nullptr;
};

3.2 查找

要实现插入,先提供一个查找关键字存不存在的功能
两个循环,内部循环用来在一个节点中查找,如果key比当前值大,key就++,如果小,就跳到它的左子树。找到返回地址和下标,找不到返回它的父节点方便插入

// 返回指针和节点中的位置
std::pair<node*, int> Find(const K& key)
{node* parent = nullptr;node* cur = _root;while (cur){// 一个节点中查找size_t i = 0;while (i < cur->_n){if (key > cur->_keys[i]){i++;}else if (key < cur->_keys[i]){break;}else{return std::make_pair(cur, i);}}parent = cur;// 往孩子跳cur = cur->_subs[i];}return std::make_pair(parent, -1);
}

在这里插入图片描述在这里插入图片描述在这里插入图片描述

插入过程总结:

1.如果树为空,直接插入新节点,该结点为树的根节点
2.树非空,找待插入元素在树中的插入位置(注意:找到的插入节点位置一定在叶子节点中)
3.检测是否找到插入位置(假设树中的key唯一,即该元素已经存在不插入)
4.按照插入排序的思想将该元素插入到找到的节点中
5.检测该结点是否满足B-树的性质:即该节点中的元素个数是否等于M,如果小于则满足
6.如果插入后节点不满足B树的性质,需要对该节点分裂

  • 申请新及诶点
  • 找到该节点的中间位置
  • 将该节点的中间位置右侧的元素以及孩子搬移到新节点中
  • 将中间位置元素以及新节点往该节点中的双亲节点中插入,即继续

7.如果向上已经分裂到根节点的位置,插入结束

4. B树的插入实现

分为两个部分,一个函数用来找到插入位置插入,一个函数进行后续的调整,分裂保证B树特征

4.1 插入数据

插入的时候走的是插入的逻辑,挪动数据的同时要挪动孩子,插入成功还要连接父亲

// 找到节点中的插入位置插入数据
void InsertKey(node* cur, const K& key, node* child)
{int end = cur->_n - 1;while (end >= 0){if (key < cur->_keys[end]){// 挪动key和它的右孩子cur->_keys[end + 1] = cur->_keys[end];cur->_subs[end + 2] = cur->_subs[end + 1];end--;}else{break;}}cur->_keys[end + 1] = key;cur->_subs[end + 2] = child;// 第一次可能为空if (child){child->_parent = cur;}cur->_n++;
}

4.2 调整

需要注意清空原数据和孩子和父亲的连接

// 插入的分裂等补充
bool Insert(const K& key)
{if (_root == nullptr){node* new_node = new node;new_node->_keys[0] = key;_root = new_node;_root->_n++;return true;}// key已经存在,不允许插入std::pair<node*, int> ret = Find(key);node* parent = ret.first;if (ret.second >= 0){return false;}// 如果没有找到,find顺便带回了要插入的叶子节点// 循环每次往cur插入,newkey和childK new_key = key;node* child = nullptr;while (1){InsertKey(parent, new_key, child);// 没满结束if (parent->_n < M){return true;}// 满了分裂node* brother = new node;size_t j = 0;// 分裂一半[mid + 1, M - 1]给兄弟size_t mid = M / 2;size_t i = mid + 1;// 拷贝key,还要拷贝孩子for (; i < M; i++){brother->_keys[j] = parent->_keys[i];brother->_subs[j] = parent->_subs[i];// 孩子不为空,更新父亲if (parent->_subs[i]){parent->_subs[i]->_parent = brother;}j++;parent->_keys[i] = K();parent->_subs[i] = nullptr;}// 还有最后一个右孩子brother->_subs[j] = parent->_subs[i];if (parent->_subs[i]){parent->_subs[i]->_parent = brother;}parent->_subs[i] = nullptr;brother->_n = j;parent->_n -= brother->_n + 1;K mid_key = parent->_keys[mid];parent->_keys[mid] = K();// 说明刚刚分裂的是头节点if (parent->_parent == nullptr){_root = new node;_root->_keys[0] = mid_key;_root->_subs[0] = parent;_root->_subs[1] = brother;_root->_n = 1;parent->_parent = _root;brother->_parent = _root;return true;}else{// 转换成往parent->parent 去插入parent->[mid] 和 brothernew_key = mid_key;child = brother;parent = parent->_parent;}}
}

4.3 B-树的简单验证

对B树中序遍历,如果得到一个有序的序列,说明插入正确。和搜索二叉树类似,先左,再根,再往右移动

void _Inorder(node* cur)
{if (cur == nullptr){return;}// 左 根 左 根 。。。 右size_t i = 0;for (; i < cur->_n; i++){_Inorder(cur->_subs[i]);  // 左子树std::cout << cur->_keys[i] << " ";  // 根}// 最后的那个右子树_Inorder(cur->_subs[i]);
}void Inorder()
{_Inorder(_root);
}

4.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之间,因此树的高度应该在 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=62*1000000000个节点,如果度M为1024,则 l o g M / 2 N log_{M/2}N logM/2N <= 4,即在620亿个元素中,如果这棵树的度为1024,则需要小于4次即可定位到该结点,然后利用二分查找可以快速定位到该元素,大大减少了读取磁盘的次数

4.6 B-树的删除

学习B树的插入足够帮助理解B树的特性,删除可以参考《算法导论》和《数据局结构-殷人昆》-C++

5. B+树和B*树

B+树是B树的变形,在B树基础上优化的多路平衡搜索树,B+树的规则跟B树基本类似,但是又在B树的基础上做了以下几点改进优化:

1.分支节点的子树指针与关键字个数相同(相当于取消了最左的子树)
2.分支节点的子树指针p[i]指向关键字值大小在[k[i],k[i+1]]区间之间
3.所有叶子节点增加一个连接指针连接在一起
4.所有关键字及其映射数据都在叶子节点出现

在这里插入图片描述
B树的特性:
1.所有关键字都出现在叶子结点的链表中,且链表中的节点都是有序的
2.不可能在分支节点命中
3.分支节点相当于是叶子节点的索引,叶子节点才是存储数据的

5.2 B+树

B*树是B+树的变形,在B+树中的非跟和非叶子点再增加指向兄弟节点的指针
在这里插入图片描述
B+树的分裂
当一个节点满时,分配一个新的节点,并将原节点中1/2的数据复制到新节点,最后在父节点中增加新及诶点的指针,B+树的分裂只影响原节点和父节点,而不会影响兄弟节点,所以它不需要指向兄弟的指针

B*树的分类
当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针。所以,B*树分配新结点的概率比B+树要低,空间使用率更高;

5.3 总结

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

内存查找B树没有优势:
1.空间利用率低。消耗高
2.插入删除数据时,分裂和合并节点,必然挪动数据
3.虽然高度更低,但是在内存而言,和哈希和平衡搜索树还是一个量级

6. B树的应用

6.1 索引

B-树最常见的应用就是用来做索引。索引通俗的说就是为了方便用户快速找到所寻之物,比如:书籍目录可以让读者快速找到相关信息,hao123网页导航网站,为了让用户能够快速的找到有价值的分类网站,本质上就是互联网页面中的索引结构。

MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构,简单来说:索引就是数据结构

当数据量很大时,为了能够方便管理数据,提高数据查询的效率,一般都会选择将数据保存到数据库,因此数据库不仅仅是帮助用户管理数据,而且数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用数据,这样就可以在这些数据结构上实现高级查找算法,该数据结构就是索引。

6.2 MySQL索引简介

mysql是目前非常流行的开源关系型数据库,不仅是免费的,可靠性高,速度也比较快,而且拥有灵活的插件式存储引擎

在这里插入图片描述
索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式不同
注意:索引是基于表的,而不是基于数据库的

6.2.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的索引方式也叫做“非聚集索引”的。

6.2.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域。
在这里插入图片描述
聚簇索引这种实现方式使得主键的搜索十分高效,但是辅助索引需要检索两变索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录,如用id和名字分别查找

B+树主键索引相比B树的优势
1.B+树所有值都在叶子,遍历很方便,方便区间查找
2.对于没有建立索引的字段,全表扫描的遍历也很方便
3.分支节点值存储key,一个分支节点空间占用更小,可以尽可能加载到内存

B树不用叶子就能找到值,B+树一定要到叶子。这是B树的优势,但是B+树高度足够低,所以差别不大

参考资料:
http://blog.codinglabs.org/articles/theory-of-mysql-index.html

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

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

相关文章

IO模型介绍

一、理解IO 网络通信的本质就是进程间通信&#xff0c;进程间通信本质就是IO TCP中的IO接口&#xff1a;read / write / send / recv&#xff0c;本质都是&#xff1a;等 拷贝 所以IO的本质就是&#xff1a;等 拷贝 那么如何高效的IO&#xff1f; 减少“等”在单位时间的…

CORDIC算法笔记整理

CORDIC算法有两种模式&#xff0c;分别为旋转模式和向量模式。而在数字硬件实现混频处理时&#xff0c;CORDIC算法是比较好的方法&#xff0c;使用的是CORDIC的旋转模式&#xff0c;只需通过移位操作和加法就可以实现频谱搬移的乘法操作。 1 CORDIC算法理解 1.1 单次旋转 对…

SpringCloud学习记录|day1

学习材料 2024最新SpringCloud微服务开发与实战&#xff0c;java黑马商城项目微服务实战开发&#xff08;涵盖MybatisPlus、Docker、MQ、ES、Redis高级等&#xff09; 学redis讲到微服务就停了&#xff0c;nginx也是。 所以嘛&#xff0c;我终于来到微服务了。 复习MyBatisP…

CMU 10423 Generative AI:lec14(Vision Language Model:CLIP、VQ-VAE)

文章目录 1 概述2 CLIP (Used in GPT-V)3 VQ-VAE (Used in Gemini)**VQ-VAE 详细笔记****VQ-VAE 的模块组成与数据流** **1. 输入数据****2. 编码器&#xff08;Encoder&#xff09;****2.1 编码器的作用****2.2 数据流与维度变化****2.3 编码器输出** **3. 量化器&#xff08;…

IP 数据包分包组包

为什么要分包 由于数据链路层MTU的限制,对于较⼤的IP数据包要进⾏分包. 什么是MTU MTU相当于发快递时对包裹尺⼨的限制.这个限制是不同的数据链路对应的物理层,产⽣的限制. • 以太⽹帧中的数据⻓度规定最⼩46字节,最⼤1500字节,ARP数据包的⻓度不够46字节,要在后⾯补填 充…

云栖实录 | 开源大数据全面升级:Native 核心引擎、Serverless 化、湖仓架构引领云上大数据发展

本文根据2024云栖大会实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a; 王 峰 | 阿里云智能集团研究员、开源大数据平台负责人 李 钰&#xff5c;阿里云智能集团资深技术专家 范 振&#xff5c;阿里云智能集团高级技术专家 李劲松&#xff5c;阿里云…

MongoDB聚合操作及索引底层原理

目录 链接:https://note.youdao.com/ynoteshare/index.html?id=50fdb657a9b06950fa255a82555b44a6&type=note&_time=1727951783296 本节课的内容: 聚合操作: 聚合管道操作: ​编辑 $match 进行文档筛选 ​编辑 将筛选和投影结合使用: ​编辑 多条件匹配: …

【AI】AIOT简介

随着技术的快速发展&#xff0c;人工智能AI和物联网IoT已经成为当今最热门的技术领域。AIOT是人工智能和物联网的结合&#xff0c;使物联网设备更加智能化&#xff0c;能够进行自主决策和学习的技术。 通过物联网产生、收集来自不同维度的、海量的数据存储于云端、边缘端&#…

数据治理006-数据标准的管理

元数据的分类和标准有哪些&#xff1f; 一、元数据的分类 元数据可以根据其描述的对象和属性不同&#xff0c;被分为不同的类型。以下是几种常见的元数据分类方法&#xff1a; 基于数据的类型&#xff1a;根据数据的类型&#xff0c;元数据可以被分为结构化元数据、非结构化元…

SQL连接Python

对于运营部门的Yoyo来说&#xff0c;她想要知道夜曲优选的订单都来自哪些省份&#xff0c;每个省份的总订单数以及总订单金额分别是多少。 这时小鹿就会通过SQL对连接的数据库进行查询&#xff0c;再将结果传递给Python处理&#xff0c;并帮助Yoyo生成可视化图表。 我们先来快…

拆解维修飞科剃须刀

原因 用了好几年的剃须刀&#xff0c;经过一次更换电池。后来上面的盖帽松动&#xff0c;无法合盖&#xff0c;经过把弹片矫正后修复。最近一次”大力出奇迹“的操作直接断送了这个老伤员最后的可能性。最终只能花了将近十块大洋买了一套盖着和中间座。简单更换了一下。 记录…

目前最好用的爬虫软件是那个?

作为一名数据工程师&#xff0c;三天两头要采集数据&#xff0c;用过十几种爬虫软件&#xff0c;也用过Python爬虫库&#xff0c;还是建议新手使用现成的软件比较方便。 这里推荐3款不错的自动化爬虫工具&#xff0c;八爪鱼、亮数据、Web Scraper 1. 八爪鱼爬虫 八爪鱼爬虫是一…

Linux:深入理解冯诺依曼结构与操作系统

目录 1. 冯诺依曼体系结构 1.1 结构分析 1.2 存储结构分布图 2. 操作系统 2.1 概念 2.2 如何管理 2.3 什么是系统调用和库函数 1. 冯诺依曼体系结构 1.1 结构分析 不管是何种计算机&#xff0c;如个人笔记本电脑&#xff0c;服务器&#xff0c;都是遵循冯诺依曼结构。…

可视化图表与源代码显示配置项及页面的动态调整功能分析

可视化图表与源代码显示配置项及页面的动态调整功能分析 文章目录 可视化图表与源代码显示配置项及页面的动态调整功能分析1.分析图表源代码2.分析源代码显示功能**完整代码参考&#xff1a;** 3.分析源代码显示及动态调整**完整代码参考&#xff1a;** 4.分析代码编辑器及运行…

华为云LTS日志上报至观测云最佳实践

华为云LTS简介 华为云云日志服务&#xff08;Log Tank Service&#xff0c;简称 LTS&#xff09;&#xff0c;用于收集来自主机和云服务的日志数据&#xff0c;通过海量日志数据的分析与处理&#xff0c;可以将云服务和应用程序的可用性和性能最大化&#xff0c;为您提供实时、…

基于SSM的爱心慈善公益网站的设计与实现

文未可获取一份本项目的java源码和数据库参考。 选题意义 随着经济的不断进步&#xff0c;发展各种进行公益事业的渠道不断的出现&#xff0c;作为一个礼仪之邦&#xff0c;中华民族一直秉承先人的团结与友善精神&#xff0c;对社会和他人给予帮助关怀。但中国的公益事业相对…

【AIGC】2022-CVPR-利用潜在扩散模型进行高分辨率图像合成

2022-CVPR-High-Resolution Image Synthesis with Latent Diffusion Models 利用潜在扩散模型进行高分辨率图像合成摘要1. 引言2. 相关工作3. 方法3.1. 感知图像压缩3.2. 潜在扩散模型3.3. 调节机制 4. 实验4.1. 关于感知压缩权衡4.2. 利用潜在扩散生成图像4.3. 条件潜在扩散4.…

防sql注入的网站登录系统设计与实现

课程名称 网络安全 大作业名称 防sql注入的网站登录系统设计与实现 姓名 学号 班级 大 作 业 要 求 结合mysql数据库设计一个web登录页面密码需密文存放&#xff08;可以采用hash方式&#xff0c;建议用sha1或md5加盐&#xff09;采用服务器端的验证码&#…

基于Hive和Hadoop的招聘分析系统

本项目是一个基于大数据技术的招聘分析系统&#xff0c;旨在为用户提供全面的招聘信息和深入的职位市场分析。系统采用 Hadoop 平台进行大规模数据存储和处理&#xff0c;利用 MapReduce 进行数据分析和处理&#xff0c;通过 Sqoop 实现数据的导入导出&#xff0c;以 Spark 为核…

英集芯IP5911:集成锂电池充电管理和检测唤醒功能的低功耗8位MCU芯片

英集芯IP5911是一款集成锂电池充电管理、咪头检测唤醒、负载电阻插拔和阻值检测等功能的8bit MCU芯片。其封装采用QFN16&#xff0c;应用时仅需极少的外围器件&#xff0c;就能够有效减小整体方案的尺寸&#xff0c;降低BOM成本&#xff0c;为小型电子设备提供高集成度的解决方…