B树学习总结

B树

多路搜索树
当数据规模足够大的时候, 大部分数据存储到外存中, 常规的平衡二叉搜索树将无法满足需求理由如下:
常规二叉平衡搜索树每当进行search(),insert(),remove()操作的时候就会进行节点的切换,从而导致大量的IO操作,就此提出了多路搜索,尝试将原本的多个节点合在一起,用于减少IO操作; 适用于在磁盘与设备上直接进行动态查找;
如下图二叉搜索及多路搜索拓扑结构:
二叉搜索与多路搜索
每个节点中具有多个关键码, 每个关键码又对应着多个引用
它与二叉平衡搜索树的区别在于,将多个节点进行合并变为关键码,减少IO操作

多路搜平衡索树
m阶B-树(m>2):

  • 在每个节点中具有不超过m-1个关键码, 并且外部节点(而非叶节点,包含一下为空的节点)的深度均相同;
  • 每个内部节点存有不超过m-1个关键码, 每个关键码所对应的分支n不超过m个引用
  • 内部节点的分支数不能太少,除根节点以外, 所有内部节点都应满足n+1>=m/2(m/2的值为向上取整), 在非空的B树中,根节点满足n>=1
  • 各节点所对应的分支数介于m/2到m之间

如:(2,4) (3,5) (4,7) (4,8)树
这里写图片描述
图中空白方格则是存储指向下一层的引用(即孩子向量), 存储数字的方格则是相应的关键码向量

B树接口

//需要用到向量vector
template<typename T>class BTNode{public:BTNode<T>* parent;//父节点vector<T> key;//用于存放关键码向量vector<BTNode<T>*> child;//存放孩子向量,用于保存下一层节点地址的指针BTNode(){parent=null; child.insert(0,null);}BTNode(T e,BTNode<T>* lc=null,BTNode<T>* rc=null){parent=null;key.insert(0,e);//B树初始化,只存有一个_root,key中保存一个关键码,俩孩子child.insert(0,lc);child.insert(1,rc);if(lc) lc-parent=this;if(rc) rc->parent=this;//孩子向量的大小总是比关键码向量大小多一个}//~BTNode(){key.clear();child.clear();},析构函数待会处理
};
template<typename T>class BTREE{protected:int _size;//关键码总数int _order;//B树阶次BTNode<T>* _root,_hot;//根节点,以及进行search访问后所指向的节点位置void solveOverflow(BTNode<T>*);//节点发生上溢进行分裂void solveUnderflow(BTNode<T>*);//节点发生下溢进行合并public:BTREE(int order=3):_order(order),size(0);{_root=new BTNode<T>();}//构造函数,默认阶数为3//  ~BTREE(){if(_root)}int const order(){return _order;}int const size(){return _size;}BTNode<T>* & root(){return _root;}bool empty()const{return !_root;}//判断树根是否为空bool insert(const T& e);bool remove(const T& e);BTNode<T>* search();
};

search实现

//对比上图,思考查找方式
//查找方式与常规的二叉平衡搜索树一致,需要指出的一点是,在每个关键码中进行查找的时候若关键码较少的时候就可直接使用顺序查找策略,而非二分查找
template<typename T>BTNode<T>* BTREE<T>::search(const T& e){BTNode<T>* v=_root;_hot=null;//从根节点查找while(v){for(int i=0;i<key.size();i++){//在当前的关键码向量中顺序查找是否有满足条件的节点if(e==v.key[i])return v;else if(e<v.key[i]){//由于关键码在向量中是顺序存放,当所要查找的关键码小于key[i],i所对应的孩子引用位置就是查找的关键码下一层所在的节点位置_hot=v;//记录查找失败的节点位置v=v->child[i];}}}return null;//遍历完所有节点还未找到
}
//查找过程中总共需要访问O(logn)个节点,尽管查找过程中没有渐进上的意义,但是极大地减少了I/O操作次数,提高了访问效率

关键码插入

template<typename T>bool BTREE<T>::insert(const T& e){BTNode<T>* v=search(e); if(v) return false;//确认目标节点不存在int r=_hot->key.find(e);//find()在此处简写,需要重写find操作,查找失败返回失败的位置(这个位置是e刚好小于该关键码的位置)//在查找失败的_hot位置处的向量内定位具体e的位置,然后进行插入操作_hot->key.insert(r,e);_hot->child.insert(r+1,null);//孩子向量总是比key向量大1,所以在r+1的位置插入一个空子树指针_size++;solveOverflow(_hot);return true;//插入成功
}

由于B树在节点插入,或者节点删除时会出现当前key向量, child向量不满足B树的定义要求, 当关键码向量出现超出当前阶数所规定的最大值时,则需要进行分裂处理, 反之小于最小值则需要进行合并处理

上溢
当刚发生上溢的时候, key中恰好有m个关键码, 以m/2为分界,前后两部分为等长的的子节点, 令m/2升入上一层, 归入父节点中合适的位置, 将子节点归入m/2的左右孩子, 如此分裂后, 就可满足m阶B-树关于节点分支数的条件
例: 以37为界进行分裂上溢操作
这里写图片描述
如果将上溢的节点添加到父节点一层又导致父节点一层发生上溢操作称之为上溢的向上传递; 但是这种传递并不是没有尽头, 最远到达树根位置, 上溢操作修复之后全树的高度将上升一层(但这种概率特别低), 整个过程中所做的分裂次数必然不会超过全树的高度,即O(logn)
根据上溢过程的处理方式, 可实现如下代码:

template<typename T>void BTREE<T>::solverOverflow(BTNode* v){if(_order >= v->child.size()) return;//递归基,当前节点未发生上溢int s=_order/2;//以此为轴点向上做上溢BTNode* u=new BTNode<T>();for(int i=0;i < _order-s-1; i++){u->child.insert(i, v->child.remove(s+1));//将前半段子节点的child节点指针与原来v->child进行分裂操作,一边复制到新节点u中,一边对原来的节点的前半段子节点进行删除操作u->key.insert(i,v->key.remove(s+1));//关键码的操作与child一致//将前部分key,child逐个移动到u中}u->child[_order-s-1]=v->child.remove(s+1);//将m/2处的孩子进行移动到u中,它不能跟着m/2上溢到父节点中if(u->child[0])//如果上述操作执行了,将会把u中的child的父节点进行统一使其不再指向vfor(int j=0 ;j< _order-s; j++)u->child[j]->parent=u;BTNode<T>* p=v->parent;if(!p){//v不具有父节点,那么则要将v中m/2处的关键码通过p上升至_root的位置_root=p=new BTNode<T>();p->child[0]=v;v->parent=p;}int r=1+p->key.find(v->key[0]);//查找key[0]在p->key的位置p->key.insert(r,v->key.remove(s)));//将v中m/2上升p->child.insert(r+1,u);//将u中的child向量插入到p中u->parent=p;//u中关键码与p相连solveOverflow(p);//有必要的话,递归进行分裂直到不再上溢为止,最多O(logn)层
}

上述过程中必须要清楚v, v->child, v->parent, u, u->child, u-parent, p, p->child 之间的含义以及联系:

  • v是要发生上溢的节点, v->child中存储着每个关键码的左右孩子,v->parent则是整个v节点的父亲
  • u中存储着v前半段的关键码以及child, 由于u是重新申请的空间需要与p重连
  • p中存储v->parent的信息以及v中上溢m/2关键码的信息,需要与v-parent, v, u, 重连

参考如下图:
这里写图片描述

关键码删除

template<typename T>bool BTREE<T>::remove(const T& e){BTNode<T>* v=search(e); if(!v) return false;//关键码未找到int r=v->key.find(e);//查找关键码在节点v中的位置if(v->child[0]){//判断是否为叶子节点,若为叶子节点则不需要将r处的v->child转移到u中BTNode<T>* u=v->child[r+1];while(u->child[0]) u=u->child[0];v->key[r]=u->key[0];v=u;r=0;//将v->child[r+1]移动至u中,完成重连,然后一路向下,找到v的直接后继,将u->key[0]把v->key[r]覆盖,使v与v->child[r+1]互换位置,v->child[r+1]代替v成为父节点 }v.key.remove(r);v->child.remove(r+1); _size--;//删除vsolveUnderflow(v);//有必要进行合并return true;
}

下溢
通过上述操作,刚好发生下溢的节点v必须恰好包含m/2-2关键码和m/2-1个分支, 可根据左右兄弟所包含关键码数目,分3中情况处理
1.v的左兄弟存在,且包含至少m/2个关键码, 右兄弟含有m/2-2个关键码:v的右兄弟向父节点p借一个关键码,使之关键码数为m/2-1, 然后父节点向v的左兄弟借一个关键码用于维持原有状态, 至此局部满足B-树的条件,整个过程可以看做是关键码之间的旋转
2. 该情况与1中的情况相反, 右兄弟存在,且包含至少m/2个关键码, 左兄弟含有m/2-2个关键码, 解决方法与1一致
3. v的左右兄弟存在或者不存在, 或者包含的关键码均不足m/2个, 解决方法可采用向父节点借一个关键码, 然后将v的左右兄弟以及借来的关键码合在一起, 此时关键码总数: (m/2-1)+1+(m/2-2)=m-2<=m-1 注:m/2做向上取整处理
上述方法完成对下溢的修复,但是有可能还会出现下溢的传递(与上溢传递情况一致); 当传递到树根的位置时,整棵树的高度将下降一层, 整个过程中至多进行O(logn)次合并操作
通过上述, 有如下代码

//整个过程中p始终代表v->parent,v代表发生下溢的向量,ls代表p在r处的左孩子,rs代表p在r处的右孩子
template<typename T>void BTREE<T>::solveUnderflow(BTNode<T>* v){if((_order+1)/2 <= v->child.size()) return;//递归基,当前节点未发生下溢BTNode<T>* p=v->parent;if(!p){//递归基,到达根节点,v代表根节点,此时的根节点已经完成了下溢的合并操作,v的内部关键码key向量已经为空if(!v->key.size() && v->child[0]){//若v中只含有child向量,key向量为空,即可以删除v,将v与_root进行重连_root=v->child[0]; _root->parent=null;v->child[0]=null]; v.clear();}return;}int r=0; while(p->child[r]!=v) r++;//确定v是p的第几个孩子,用于后续的关键码之间的三角旋转//情况1: 向左兄弟借关键码if(0<r){//v不是p的第一个孩子,说明左兄弟必然存在BTNode<T>* ls=p->child[r-1];if((_order+1)/2 < ls->child.size()){//左兄弟快发生上溢,则向左兄弟借出一个节点v->key.insert(0,p->key[r-1]);//先将p的关键码给v,再将v左兄弟的关键码给p//p借出后无需进行remove,直接通过v左兄弟的关键码将其覆盖p->key[r-1]=ls->key.remove(ls->size()-1);//由于关键码的转移,所以要将转移的关键码所对应的child进行转移v->child.insert(0,ls->child.remove(ls->child.size()-1));if(v->child[0])//判断转移后的孩子是否为空,不为空,则进行重连v->child[0]->parent=v;return;//完成当前层的下溢操作}}//情况2: 向右兄弟借关键码if(p->child.size()-1>r)//v不是p的最后一个孩子,说明右兄弟必然存在BTNode<T>* rs=p->child[r+1];if((_order+1)/2 < rs->child.size()){v.key.insert(v->key.size(),p.key[r])p->key[r]=rs->key.remove(0);v->child.insert(v->child.size(),rs->child.remove(0));if(v->child[v->key.size()-1])v->child[v->key.size()-1]->parent=v;return;}//情况3:向p中借出r位置的节点,使r的左右孩子进行合并操作if(0<r){//左兄弟存在,与左兄弟进行合并BTNode<T>* ls=p->child[r-1];//左兄弟存在,将p中r处的关键码转移至ls中ls->key.insert(ls->key.size(),p->key.remove(r-1));if(ls->child[ls->child.size()-1])//将v最左侧的孩子转移到ls中,做ls最右侧的孩子while(!v->key.empty()){//合并过程,将v中的信息全部转移至ls中ls->key.insert(ls->key.size(),v->key.remove(0));//一边对ls进行插入,一边对v进行删除ls->child.insert(ls->child.size(),v->child.remove(0));if(ls->child[ls->child.size()-1])//判断转移过来的孩子是否为空,不为空则进行child->parent与ls的重连ls->child[ls->child.size()-1]->parent=ls;}v.clear();}else{//右兄弟存在,与右兄弟合并BTNode<T>* rs=p->child[r+1];rs->key.insert(0,p->key.remove(r+1));//将p中r+1处的关键码转移至右兄弟中p->child.remove(r);//删除p中r+1处的childrs->child.insert(0,v->child.remove(v->child.size()-1));//孩子的转移if(rs->child[0])//此处做法与右兄弟中的操作一致rs->child[0]->parent=rs;while(!v->empty()){//将v中关键码,孩子向量一一转至到rs中rs->key.insert(0,v->key.remove(v->key.size()-1));rs->child.insert(0,v->child.remove(v->child.size()-1));if(rs->child[0])//rs的孩子存在,进行重连rs->child[0]->parent=rs;}v.clear();}solveUnderflow(p);//上升一层,如果有必要进行继续分裂return;
}

上述过程如下图:
这里写图片描述
删除操作的复杂度依然可以保证在O(logn)内,单次操作平均只需做常数次节点的合并

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

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

相关文章

嵌入式未来趋势是什么?

感谢CSDN编辑邀请 前几天通过了CSDN博客专家的申请&#xff0c;在CSDN博客摸爬滚打多少个日日夜夜后终于修成正果了&#xff0c;当时通过CSDN博客专家时赶紧把消息发给了以前的创新基地同学&#xff0c;想起来&#xff0c;我们那时候刚开始学习单片机&#xff0c;就是从CSDN上面…

js动态添加删除节点

转载于:https://www.cnblogs.com/jiaobaobao/p/6762692.html

书中自有黄金屋~外加中奖结果通知

人生的路很长&#xff0c;很艰辛&#xff0c;只有不断学习才能超越梦想&#xff0c;大牛是如何成长的&#xff01;首先要多看多听多写~~今天推荐一些大牛们都关注的技术公众号&#xff0c;机器学习、架构、前端、嵌入式、PY学习.....希望能够帮助到大家&#xff0c;引领我们前行…

拨号云服务器怎么自动配置网关_云服务器配置网站卡慢怎么办

网站访问卡慢有很多原因&#xff0c;一次完整的 HTTP 请求包括域名解析、建立 TCP 连接、发起请求、云服务器接收到请求进行处理并返回处理结果、浏览器对 HTML 代码进行解析并请求其他资源&#xff0c;以及对页面进行渲染呈现。其中&#xff0c;HTTP 的请求过程经历了用户本地…

手机java软件_浅谈软件开发就业前景

​  我国信息化人才培养还处于发展阶段&#xff0c;导致社会实际需求人才基数远远大于信息化人才的培养基数&#xff0c;使得数以万计的中小企业急需全面系统掌握软件开发基础技能与知识的软件工程师。目前对软件已达20万并且以每年20%左右的速度增长。在未来5年内合格软件人…

ufldl学习笔记与编程作业:Multi-Layer Neural Network(多层神经网络+识别手写体编程)...

ufldl学习笔记与编程作业&#xff1a;Multi-Layer Neural Network(多层神经网络识别手写体编程) ufldl出了新教程&#xff0c;感觉比之前的好&#xff0c;从基础讲起&#xff0c;系统清晰&#xff0c;又有编程实践。 在deep learning高质量群里面听一些前辈说&#xff0c;不必深…

sql查询无结果返回空_3分钟短文 | Laravel 查询结果检查是不是空,5个方法你别用错...

引言Laravel 提供了 Eloquent ORM 对象用于操作数据库&#xff0c;将其进行抽象方便操作。因为设计的灵活度&#xff0c;大家在使用Model查询数据集的时候&#xff0c;会面临结果为空&#xff0c;记录不存在的问题&#xff0c; 那么如何有效地判断查询记录为空呢&#xff1f;本…

深度优先搜索小结

深度优先搜索(DFS) 深度优先搜索就好比走迷宫, 不断顺着一条路走, 直到走不通为止, 然后回退到上一个路口再向另外的方向行走(走过的方向就不会再走了,又不是傻子, 知道走不通,还向走不通的方向走), 不断重复(试过所有路口, 状态转移), 重复直到找到唯一的一条合适的路径; DFS…

python 串口_如何使用Python开发串口通讯上位机(一)

用Python开发串口通讯型上位机&#xff0c;其实并非最优解&#xff0c;本系列更新只为个人学习与总结。如果有C语言底子&#xff0c;嵌入式层面的上位机开发&#xff0c;C Builder&#xff0c;C#才是更加好用的利器。1什么是上位机从事过嵌入式软件开发或者工控机开发的&#x…

算法题之求二叉树的最大距离

二叉树是一种非常经典的数据结构。如果我们把二叉树看成一个图&#xff0c;父子节点之间的连线看成是双向的&#xff0c;我们姑且定义"距离"为两节点之间边的个数。写一个程序求一棵二叉树中相距最远的两个节点之间的距离。 下面我们随意构造出一棵二叉树&#xff0c…

php 公众号验证回调方法_微信公众号关键词自动回复设置方法!

什么是公众号关键词自动回复&#xff1f;在微信公众号平台设置关键词自动回复&#xff0c;可以通过添加规则&#xff0c;关注/订阅的用户发送的消息内容如果是你设置的关键字&#xff0c;即可以实现自动回复预先设置好的内容。关键字自动回复设置方法&#xff1a;1、 首先我们进…

图文|Android 使用Thread 和多线程使用互斥锁

为什么需要多线程进行开发&#xff1f;多线程不管是嵌入式系统RTOS&#xff0c;Linux&#xff0c;还是应用开发&#xff0c;中间件开发&#xff0c;都是必不可少的&#xff0c;做一个技术的时候&#xff0c;如果能做到举一反三&#xff0c;下次使用的时候不会再遇到坑&#xff…

计算机专业的学生该选择日后的人生道路?继续从事IT还是考公务员……

问题来自知乎原问题如下&#xff1a;知乎上的盆友们大家好&#xff01;我是某高校大二学生。万能的知友们&#xff0c;有相关经验的大家给点宝贵意见呗&#xff0c;万分感谢。个人问题&#xff1a;我当时是听说这一行工资高&#xff0c;因为家里条件一般&#xff0c;所以想要挣…

汇编比较两个数大小_计算机是怎样跑起来的 -- 体验一次汇编过程

标志内存或I/O中存储单元的数字叫做“地址”。CPU中的标志寄存器有什么作用&#xff1f;用于在运算指令执行后&#xff0c;存储运算结果的某些状态。从程序员的角度看硬件CPU&#xff1a;种类&#xff0c;时钟信号的频率。可以使用哪种机器语言取决于CPU的种类。内存信息&#…

android区块链 钱包_区块链钱包Cashbox 开发工程师聊一聊开源

今天与大家聊一聊软件开源。事情的起因是一次闲谈的时候&#xff0c;市场部的同事表示&#xff1a;不做软件开发的人不能够理解为什么要把软件源代码公开出去。当时的我很惊讶&#xff0c;因为就像他们不理解为什么要开源一样&#xff0c;我也不理解他们为什么不理解为什么要开…

最新车载导航端口检测工具_高德地图这个功能 把微信都没做好的车载社交解决了?...

在汽车网联化和智能化带给人们的诸多想象里&#xff0c;车内社交似乎是最虚无缥缈的那个。在移动互联网领域&#xff0c;微信通过满足人们社交需求这个基本点&#xff0c;构建起庞大的用户群体&#xff0c;展示出巨大的市场前景。但在驾车场景下&#xff0c;车载社交该怎么玩&a…

王译潇20162314 第九周作业总结

学号 20162314 2016-2017-2 《Java程序设计》第九周学习总结 教材学习内容总结 数据库是为其他程序提供数据的应用软件 关系数据库通过唯一的标识符在不同表的记录间建立了关系 JDBC API用来建立到数据库的连接 CREATE TABLE SQL 语句用来创建新的数据库表 ALTER TABLE SQL 语句…

sketchup边线设置_春天花花天桥,SketchUp草图大师快速建模!

最近小吧在网上看到一座设计很特别的天桥&#xff0c;形状很像一朵花&#xff0c;名字也十分写实——春花天桥。喏&#xff0c;就是下面这个家伙&#xff01;图片来自网络春花天桥是2011年深圳举办大运会前夕投资建设的形象提升工程之一&#xff0c;在建设初期就被定位为地标性…

android 7.1 apk的systemuid和系统应用Setting相同导致开机找不到库的问题

1、前言Android apk加载机制这就是我最近在解决的一个问题&#xff0c;32位的apk可以用32位的库&#xff0c;那64位的apk可以用64位的库&#xff0c;如果我想64位的apk同时使用32和64位的库呢&#xff1f;android加载so文件的机制apk在安装的过程中&#xff0c;系统就会对apk进…

怎样的人适合当码农?

1高中的时候&#xff0c;我一个友仔&#xff0c;他说要创办一个科技协会&#xff0c;然后跟我说电脑里面的代码都是0和1组成的&#xff0c;我现在还记得那天我们在学校开会的时候讨论电脑如何运行&#xff0c;我一脸痴呆的听他给我讲解电子知识&#xff0c;而且他后面自己做了一…