4.B-树

一、常见的查找方式 

顺序查找 O(N)

二分查找 O(logN)(要求有序和随机访问)

二叉搜索树 O(N)

平衡二叉搜索树(AVL树和红黑树) O(logN)

哈希 O(1)
考虑效率和要求而言,正常选用 平衡二叉搜索树哈希 作为查找方式。

但这两种结构适合用于数据量相对不是很大,能够一次性存放在内存中,进行数据查找的场景。如果数据量很大,比如有100G数据,无法一次放进内存中,那就只能放在磁盘上了。

如果放在磁盘上,有需要搜索某些数据,那么如果处理呢?

可以考虑将 存放关键字及其映射的数据的地址放到一个内存中的搜索树的节点 中。需要访问数据时,先取这个地址去磁盘访问数据。

分析效率的缺陷:

使用平衡二叉树搜索树的缺陷:
平衡二叉树搜索树的高度是 logN ,这个查找次数在内存中是很快的。但是当数据都在磁盘中时,访问磁盘速度很慢,在数据量很大时,logN 次的磁盘访问,是一个难以接受的结果。
使用哈希表的缺陷:
哈希表的效率很高是 O(1) ,但是一些极端场景下某个位置冲突很多,导致访问次数剧增,也是难以接受的。
那如何加速对数据的访问呢?
1. 提高 IO 的速度 (SSD 相比传统机械硬盘快了不少,但是还是没有得到本质性的提升 )
2. 降低树的高度 --- 多叉树平衡树

二、B树概念

1970 年, R.Bayer E.mccreight 提出了一种适合外查找的树,它是一种平衡的多叉树,称为 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 其中, Ki(1≤i≤n) 为关键字,且Ki<Ki+1(1≤i≤n-1) Ai(0≤i≤n) 为指向子树根结点的指针。且 Ai 所指子树所有结点中的 关键字均小于Ki+1。 n为结点中关键字的个数,满足 ceil(m/2)-1≤n≤m-1

三、B树的插入

抽象情况分析:

1)根为空,创建新根,插入

2)根不为空,查找key,

若存在,不插入;

若不存在,插入到对应的叶子节点,若没有满,插入结束;若满了就分裂出兄弟节点,分一半key和孩子给兄弟节点,提取key中位数,转化成向父节点插入key中位数和兄弟节点,直到父节点满足条件:没有满 或者 父节点的父节点为空,产生新的根节点。

具体情况分析:

以{53,139,75,49,145,36,50,47,101}为例:

插入53,139,75:

插入49,145,36:

插入50,47,101:

实现插入时特别注意:

1)分裂出兄弟节点时,拷贝一半key和一半孩子,最后剩一个右孩子拷贝;拷贝的孩子若不为空,要链接父节点指针。

2)兄弟节点的_n和原始节点的_n都要修正

3)兄弟节点的父指针要指向cur->parent

4)若cur的父节点为空,造新根,新根左为cur,右为brother,注意链接cur和brother的父指针。

5)若cur的父节点不为空,cur迭代到cur->parent,转化成向cur->parent插入midKey和brother。

循环直到 当前节点未满造出新根(4)。

insertKey细节:用index迭代从头遍历,直到找到比index大的位置,index及以后位置都往后挪动一位,同时挪动右孩子。若index一直走到尾,说明key是最大值,在尾赋值。最后统一链接右孩子,_n++。

代码实现如下:

void insertKey(Node* cur, Node* sub, const K& key)
{size_t index = 0;while (index < cur->_n){//key大于当前位置,往后走if (key > cur->_keys[index]){++index;}else{//把index及以后key往后挪动一位//右孩子都向右挪动一位for (int i = cur->_n - 1; i >= (int)index; --i){cur->_keys[i + 1] = cur->_keys[i];cur->_subs[i + 2] = cur->_subs[i + 1];}cur->_keys[index] = key;break;}}if (index == cur->_n){cur->_keys[index] = key;}//链接右孩子cur->_subs[index + 1] = sub;cur->_n++;
}bool insert(const K& key)
{if (_root == nullptr){_root = new Node;_root->_keys[0] = key;_root->_n++;return true;}pair<Node*, int> ret = find(key);//不允许冗余if (ret.second != -1){return false;}//cur为要插入的叶子结点Node* cur = ret.first;K newkey = key;Node* brother = nullptr;while (true){insertKey(cur, brother, newkey);//未满,结束if (cur->_n < M){break;}//满了,分裂出兄弟节点brother = new Node;size_t mid = M / 2;size_t k = 0;//左key1 左孩子1 ... 左keyM 左孩子M 右孩子for (size_t i = mid + 1; i < M; ++i){//分一半keybrother->_keys[k] = cur->_keys[i];cur->_keys[i] = K();//分左孩子brother->_subs[k] = cur->_subs[i];//链接父指针if (brother->_subs[k] != nullptr){brother->_subs[k]->_parent = brother;}cur->_subs[i] = nullptr;++k;}//最后一个右孩子brother->_subs[k] = cur->_subs[M];//链接父指针if (brother->_subs[k] != nullptr){brother->_subs[k]->_parent = brother;}cur->_subs[M] = nullptr;//修正borther的_n和cur的_n(cur->_n还要提一个中位数给parent)brother->_n += M - mid - 1;cur->_n -= M - mid;//brother的parent指向cur->_parentbrother->_parent = cur->_parent;newkey = cur->_keys[mid];cur->_keys[mid] = K();//转换成向parent插入一个midkey和brotherif (cur->_parent == nullptr){_root = new Node;_root->_keys[0] = newkey;_root->_subs[0] = cur;_root->_subs[1] = brother;cur->_parent = _root;brother->_parent = _root;_root->_n++;break;}else{cur = cur->_parent;}}return true;
}

 B树的查找:

分析:每个节点的key都是有序序列,且是一维数组,支持随机访问,因此选用二分查找。

问题就是如果二分查找当前序列找不到怎么办?

二分查找如果找不到,找出来的数据是可能比key大,也可能比key小的。但由于二分查找定位的特性,找出来的数据一定是keys数组中和与key最接近的二者之一,可能在key左边,也可能在右边,判断一下就行了,比key小就在key的右孩子里找,反之在key的左孩子里找。

注:由于查找在插入时也要用,要用parent记录cur的parent,以便于走到空时能记录下叶子节点的指针。

代码试下如下:

pair<Node*, int> find(const K& key)
{Node* cur = _root;Node* parent = nullptr;int begin, end, mid;while (cur){begin = 0;end = cur->_n - 1;//二分查找keywhile (begin <= end){mid = begin + ((end - begin) >> 1);// mid keyif (cur->_keys[mid] < key){begin = mid + 1;}//key midelse if (cur->_keys[mid] > key){end = mid - 1;}else{return { cur,mid };}}parent = cur;//没有找到,mid是有效位置//可能大于或者小于keyif (cur->_keys[mid] < key){cur = cur->_subs[mid + 1];}else{cur = cur->_subs[mid];}}//cur走到nullptr,没找到,返回叶子节点的指针return { parent,-1 };
}

B树的中序遍历:

分析:采用一个左孩子,一个关键字的方式遍历,最后遍历剩余的一个右孩子。

代码如下:

void inorder()
{_inorder(_root);cout << endl;
}
void _inorder(Node* root)
{if (root == nullptr)return;//左1 根1 ... 左_n 根_n 右for (size_t i = 0; i < root->_n; ++i){_inorder(root->_subs[i]);cout << root->_keys[i] << " ";}_inorder(root->_subs[root->_n]);
}

B树性能分析:

B- 树的效率是很高的,对于 N = 62*1000000000 个节点,如果度 M 1024,则logM/2(​N)≤4 ,即在620 亿个元素中,如果这棵树的度为 1024 ,则需要小于 4 次即可定位到该节点,然后利用 二分查找可以快速定位到该元素,大大减少了读取磁盘的次数
对于一颗满的B树, 查找的时间复杂度为O(logM(N))。

四、B+树和B*

B+树:

B+ 树是 B 树的变形,是在 B 树基础上优化的多路平衡搜索树, B+ 树的规则跟 B 树基本类似,但是又
B 树的基础上做了以下几点改进优化:
1)分支节点的子树指针与关键字个数相同
2)分支节点的子树指针 p[i] 指向关键字值大小在 [k[i] k[i+1]) 区间之间
3)所有叶子节点增加一个链接指针链接在一起(核心)
4)所有关键字及其映射数据都在叶子节点出现(核心)
分析:1)和 2)优化了B树操作的便捷性,等同于删除了第一个节点的左孩子,且2)的优化存节点的最小值,进一步提高了查找的效率。
3)和 4)优化了B树的结构,将所有关键字及其映射数据都在叶子结点出现,并且叶子节点之间相连,方便遍历。
B+ 树的特性:
1. 所有关键字都出现在叶子节点的链表中,且链表中的节点都是有序的。
2. 不可能在分支节点中命中。
3. 分支节点相当于是叶子节点的索引,叶子节点才是存储数据的数据层。

B*树:

B* 树是 B+ 树的变形,在 B+ 树的非根和非叶子节点再增加指向兄弟节点的指针。
B* 树的分裂:
当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3 的数据到新结点,最后在父结点增加新结点的指针。
总结:当一个节点满时,转移到下一个兄弟,直到所有的节点都满了,分裂新的节点。
B* 树分配新结点的概率比 B+ 树要低,空间使用率更高。
总结
B 树:有序数组 + 平衡多叉树;
B+ 树:有序数组链表 + 平衡多叉树;
B* 树:一棵更丰满的,空间利用率更高的 B+ 树。

在内存中做内查找: 

B树系列与哈希和平衡二叉搜索树对比:

单纯论树高度,搜索效率而言,B树快。

B树系列隐形坏处:

1)空间利用率低,消耗高

2)插入删除数据时,分裂和合并节点,那么必然挪动数据

3)在内存中而言,跟哈希和平衡二叉搜索树还是一个量级。

结论:实质上B树系列在内存中体现不出优势。

五、B-树的应用

1.索引
B- 树最常见的应用就是用来做索引
MySQL 官方对索引的定义为: 索引 (index) 是帮助 MySQL 高效获取数据的数据结构,简单来说: 索引就是数据结构
2.MySQL 索引简介
mysql 是目前 非常流行的开源关系型数据库,不仅是免费的,可靠性高,速度也比较快,而且拥 有灵活的插件式存储引擎。
MySQL 中索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的。
意:索引是基于表的,而不是基于数据库的。
3.MyISAM
MyISAM 引擎是 MySQL5.5.8 版本之前默认的存储引擎,不支持事物支持全文检索,使用 B+Tree作为索引结构,叶节点的data 域存放的是数据记录的地址,其结构如下:
MyISAM 的索引文件仅仅保存数据记录的 地址 MyISAM 中,主索引和辅助索引( Secondary key )在结构上没有任何区别,只是主索 引要求 key 是唯一的,而辅助索引的 key 可以重复。
Secondary key同样也是一棵B+Tree data 域保存数据记录的地址。因此, MyISAM 中索引检索的算法为首先按照B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址,读取相应数据记录。MyISAM 的索引方式也叫做 非聚集索引 的。
分支节点只存储key,比较小。
分支节点映射的磁盘块就可以尽可能加载Cache。
对于SQL分析:
update stuInfo set age = 35 where stuID = 50
update stuInfo set age = 35 where name = 'Rose'
这两句SQL语句的性能不同。
第一种,stuID为主键,主索引,结构是一颗B+树,查找效率为O(logM(N))
第二种,name不是主键,且没有索引,查找时暴力遍历,效率O(N)
若想经常用name进行查找, 给name创建普通索引,相当于使用name做key创建一颗B+树,value指向磁盘数据,这时候查找效率就高了。
B+树做主键索引比起B树的优势:
1)B+树所有值都在叶子,遍历很方便,方便区间查找。
2)对于没有建立索引的字段,全表扫描很方便。
3)分值节点存储key,一个分支节点占用空间更少,可以尽可能加载到缓存。
B树优势:不用带叶子就能查找到值,但B+树高度不高,效率差别不大。
4.InnoDB:
InnoDB 存储引擎支持事务 ,其设计目标主要面向在线事务处理的应用,从 MySQL 数据库 5.5.8 本开始, InnoDB 存储引擎是默认的存储引擎 InnoDB 支持 B+ 树索引、全文索引、哈希索引。但InnoDB使用 B+Tree 作为索引结构时,具体实现方式却与 MyISAM 截然不同。
第一个区别是 InnoDB 的数据文件本身就是索引文件 MyISAM 索引文件和数据文件是分离的, 索引文件仅保存数据记录的地址
最大区别:每个叶子节点数据都是以文件形式存到磁盘上,数据和主键索引放在一起。 叶节点包含了完整的数据记录, 这种索引叫做聚集索引
所以 InnoDB 要求表必须有主键 MyISAM 可以没有), 如果没有显式指定,则 MySQL 系统会自动选择一个可以唯一标识数据记录的列作为主键 如果不存在这种列,则 MySQL 自动为 InnoDB 表生成一个隐含字段作为主键,这个字段长度为6 个字节,类型为长整型。
第二个区别是 InnoDB 的  辅助索引data域存储相应记录主键的值而不是地址  , 所有辅助索引都引用主键作为data 域。
聚集索引这种实现方式使得按主键的搜索十分高效 ,但是 辅助索引搜索需要检索两遍索引:首先 检索辅助索引获得主键,然后用主键到主索引中检索获得记录。

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

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

相关文章

CTF--shell

一、原题 &#xff08;1&#xff09;提示&#xff1a; $poc"a#s#s#e#r#t";$poc_1explode("#",$poc);$poc_2$poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5]; $poc_2($_GET[s]) &#xff08;2&#xff09;原网页&#xff1a;一片空白什么都…

基于51单片机的正负5V数字电压表( proteus仿真+程序+设计报告+讲解视频)

基于51单片机的正负5V数字电压表( proteus仿真程序设计报告讲解视频&#xff09; 仿真图proteus7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0101 1. 主要功能&#xff1a; 设计一个基于51单片机数字电压表 1、能够…

hive数仓要点总结

1.OLTP和OLAP区别 OLTP&#xff08;On-Line Transaction Processing&#xff09;即联机事务处理&#xff0c;也称为面向交易的处理过程&#xff0c;其基本特征是前台接收的用户数据可以立即传送到计算中心进行处理&#xff0c;并在很短的时间内给出处理结果&#xff0c;是对用…

【实战手册】8000w数据迁移实践:MySQL到MongoDB的完整解决方案

🔥 本文将带你深入解析大规模数据迁移的实践方案,从架构设计到代码实现,手把手教你解决数据迁移过程中的各种挑战。 📚博主其他匠心之作,强推专栏: 小游戏开发【博主强推 匠心之作 拿来即用无门槛】文章目录 一、场景引入1. 问题背景2. 场景分析为什么需要消息队列?为…

运行小程序需要选择什么配置的服务器

主要是看有多少人浏览&#xff0c;如果是每天有几十个人浏览&#xff0c;通常2核或者4核就可以满足需求&#xff0c;内存的话建议4g或者8g&#xff0c;足够的内存可以使服务器同时处理多个请求&#xff0c;避免因内存不足导致的卡顿或程序崩溃。 硬盘存储方面&#xff0c;50GB…

基于SpringBoo的地方美食分享网站

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

Solidity私有函数和私有变量区别,私有变量可以被访问吗

web3面试题 私有函数和私有变量区别&#xff0c;私有变量可以被访问吗 ChatGPT said: 在 Web3 开发&#xff0c;尤其是使用 Solidity 编写智能合约时&#xff0c;关于私有函数和私有变量的区别是常见的面试题。下面是详细解析&#xff1a; ✅ 私有函数&#xff08;Private Fu…

mongodb 安装配置

1.下载 官网下载地址&#xff1a;MongoDB Community Download | MongoDB 2.使用解压包 解压包安装&#xff1a;https://pan.baidu.com/s/1Er56twK9UfxoExuCPlJjhg 提取码: 26aj 3.配置环境&#xff1a; &#xff08;1&#xff09;mongodb安装包位置&#xff1a; &#xf…

多模态大语言模型arxiv论文略读(十九)

MLLMs-Augmented Visual-Language Representation Learning ➡️ 论文标题&#xff1a;MLLMs-Augmented Visual-Language Representation Learning ➡️ 论文作者&#xff1a;Yanqing Liu, Kai Wang, Wenqi Shao, Ping Luo, Yu Qiao, Mike Zheng Shou, Kaipeng Zhang, Yang Yo…

[LeetCode 45] 跳跃游戏2 (Ⅱ)

题面&#xff1a; LeetCode 45 跳跃游戏2 数据范围&#xff1a; 1 ≤ n u m s . l e n g t h ≤ 1 0 4 1 \le nums.length \le 10^4 1≤nums.length≤104 0 ≤ n u m s [ i ] ≤ 1000 0 \le nums[i] \le 1000 0≤nums[i]≤1000 题目保证可以到达 n u m s [ n − 1 ] nums[…

前端面试宝典---闭包

闭包介绍 使用闭包&#xff1a; 在函数内声明一个变量&#xff0c;避免外部访问在该函数内再声明一个函数访问上述变量&#xff08;闭包&#xff09;返回函数内部的函数使用完毕建议闭包函数null;译放内存 function createCounter() {let count 0;return function () {coun…

GPT4O画图玩法案例,不降智,非dalle

网址如下&#xff1a; 玩法1&#xff1a;吉卜力&#xff08;最火爆&#xff09; 提示词&#xff1a;请将附件图片转化为「吉卜力」风格&#xff0c;尺寸不变 玩法2&#xff1a;真人绘制 提示词&#xff1a;创作一张图片&#xff0c;比例4:3&#xff0c;一个20岁的中国女孩…

4.12~4.14【Q】cv homework6

我正在写GAMES101作业6&#xff0c;在这段代码中&#xff0c;我十分想知道inline Intersection Triangle::getIntersection(Ray ray) 是由哪个函数&#xff0c;哪段代码调用的&#xff1f;什么是Inline&#xff1f;详细解释&#xff0c;越细节越好 我正在写GAMES101作业6&…

MATLAB双目标定

前言&#xff1a; 现在有许多双目摄像头在出厂时以及标定好&#xff0c;用户拿到手后可以直接使用&#xff0c;但也有些双目摄像头在出厂时并没有标定。因而这个时候就需要自己进行标定。本文主要介绍基于matlab工具箱的自动标定方式来对双目相机进行标定。 1、MATLAB工具箱标…

visual studio 常用的快捷键(已经熟悉的就不记录了)

以下是 Visual Studio 中最常用的快捷键分类整理&#xff0c;涵盖代码编辑、调试、导航等核心场景&#xff1a; 一、生成与编译 ​生成解决方案 Ctrl Shift B 一键编译整个解决方案&#xff0c;检查编译错误&#xff08;最核心的生成操作&#xff09;​编译当前文件 Ctrl F…

Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.

今天写项目的时候碰到一个报错&#xff0c;在网上查找到了解决方法&#xff0c;这里备份一下。防止下次再次遇到 原文章链接&#xff1a;Sass import rules are deprecated and will be removed in Dart Sass 3.0.0. 报错内容如下&#xff1a; Deprecation Warning: Sass i…

【QT】QWidget 概述与核心属性(API)

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Qt 目录 一&#xff1a;&#x1f525; 控件概述 &#x1f98b; 控件体系的发展阶段 二&#xff1a;&#x1f525; QWidget 核心属性 &#x1f98b; 核心属性概览&#x1f98b; 用件可用&#xff08…

Redis 在处理并发请求时,如何保证高效性和数据一致性

1. 单线程模型&#xff08;核心命令处理&#xff09; 单线程优势&#xff1a;Redis 的核心命令处理是单线程的&#xff08;基于内存操作&#xff0c;避免多线程竞争&#xff09;&#xff0c;所有命令按顺序执行&#xff0c;天然避免了多线程的锁竞争和上下文切换开销。非阻塞 …

flutter-Text等组件出现双层黄色下划线的问题

文章目录 1. 现象2. 原因3. 解决方法 1. 现象 这天我正在写Flutter项目的页面功能&#xff0c;突然发现我的 Text 文字出现了奇怪的样式&#xff0c;具体如下&#xff1a; 文字下面出现了双层黄色下划线文字的空格变得很大&#xff0c;文字的间距也变得很大 我百思不得其解&a…

cursor+高德MCP:制作一份旅游攻略

高德开放平台 | 高德地图API (amap.com) 1.注册成为开发者 2.进入控制台选择应用管理----->我的应用 3.新建应用 4.点击添加Key 5.在高德开发平台找到MCP的文档 6.按照快速接入的步骤&#xff0c;进行操作 一定要按照最新版的cursor, 如果之前已经安装旧的版本卸载掉重新安…