数据结构之B-树,你每天都在用的,源码发布!
五一前就筹划着写下这篇文章,但是迫于自己从来没有实现过B-树(如果大家感兴趣,我可以考虑写一篇B+树的文章),手中没有源代码,另外自己以前对B-树也是一知半解状态中,担心误人子弟,在4月30日终于把代码写完,今天调完之前的bug之后,那种感觉就像在鸟无人烟的大荒漠中走了好久,看到一间有水的屋子,长舒一口气!好的废话不多说,下面直接切入正题!
链表,树,图是最基本的数据结构了,链表有单链表、双链表,有环和无环等等;树有二叉树、多叉树,平衡树、不平衡树等等;图有有向、无向图等等。如果把算法建模看成是建筑师建造一座大厦,那么数据结构就是地基,地基的好坏直接关系到房子能建多高。
今天我们要讲到的是树的一种,即B-树。要说B-树,就必然要说平衡二叉树,否则这是不科学的,没有平衡二叉树哪来B-树。它的定义是这样的:
平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
图一
这个是一颗典型的平衡二叉树,对根节点50而言,左右子树的高度都是3,高度差绝对值不超过1;对左子树而言,根节点是17,但是他的左右两子树高度都是2,差的绝对值也不超过1.我们递归的观察,显然它符合平衡二叉树的基本条件。
如果我们把右子树剪掉,他就不是平衡二叉树了。左边高度为3,右边高度为0;3-0=3>1.
图二
OK,说到这里,我想我们可以讲讲B-树了。B-树其实可以理解为多叉平衡树!它的定义是这样的:(其中T一般大于3)
1)不仅高度平衡,而且所有的叶子节点都在同一层。
2)除根节点外其他每个节点最少保存(T-1)个关键字,至多保存(2T-1)个关键字;至少保存(T)个孩子结点,至多(2T)个孩子结点。
3)对于关键字K而言,左孩子结点的所有关键字都小于K,右孩子结点的所有关键字都大于K。在同一个结点上,关键字是从小到大依次排列。结点中除叶子节点关键字外,均有左右孩子。
随便提一下,现在有很多B-树的变形,有些要求每个节点至少保存2/3(2T-1)个关键字等等,也是可以的。
如下图就是一颗典型的B-树:
图三
OK,对于B-树,想必大家有了一个直观的认识。那么B-树有什么用呢?当今社会太浮躁了,没用的东西留着干嘛?果断弃之。显然B-树是非常有用的。大家用数据库进行开发吗?如果你用,那么其实你每天都在用B-树有没有,因为大部分数据库的底层实现都是用B-树。
B-树对大数据特别有用,特别是查询情况多的时候(比如数据库);因为一旦数据量特别大之后,内存肯定不能把所有数据都装到内存中,我们唯有通过硬盘存储,存储在硬盘中之后,怎样才能高效的进行查询呢?我们仔细观察B-绝对是一个很好的选择,因为它最糟糕的时候效率都是logT(N);硬盘的速度相对内存来说是比较慢的,较少一次硬盘读取对用户体验是一个莫大的支持!
老规矩:如果需要全部源代码请点赞后留下email地址。
其实基于B-的数据库,数据操作基本可以归纳为四个部分:
读取硬盘数据:根据给定页面Id读取硬盘数据到内存,然后返回内存相应位置;
硬盘写操作:给定内存需写入数据地址信息,写入到给定页面id硬盘中。
申请硬盘页面:如果页面中id不存在,开拓硬盘页面,然后返回内存地址。
释放页面:给定一个硬盘页面id,释放该资源。
OK,我们了解到了B-树的重要性之后,也就知道了学习B-树的现实意义了。那我们现在可以继续开扒了。
B-树应该怎么设计呢?看了B-树的定义之后,B-树数据结构应该包括:1)关键字个数,2)指向父节点的指针,3)关键字集合,4)孩子结点集合。
根据以上论述,我们把B-树的数据结构代码化之后如下:
struct BTreeNode{
int keyNum;//关键字个数。
BTreeNode* parentNode;//父结点指针
vector<NodeValueType> nodeValues;
vector<BTreeNode*> childs;
BTreeNode(){
keyNum=;
parentNode=NULL;
}
};
对于B-树而言最常用的就是增删改查了。改可以认为是删,然后增。所以可以理解成主要操作有增、删、查!下面分三部分详解他们。
B-树的查询操作:
B-树的查询可以从根节点开始查找,根节点没有查到,再查找某一个孩子结点。
因为B-树结点都有很多孩子节点,查找哪个孩子结点就显得特别重要了,总不能乱查吧。它应该是查找第一个比关键字大的左结点,如果没有一个比该关键字大的关键字,则查找最右孩子。如此递归。直到查到叶子结点,如果叶子结点也没有找到,那么B-树中不存在该关键字。
比如对于图三,查找关键字G。首先在根节点中查找,根节点只有M一个关键字,显然不符合条件。查找到第一个大于G的关键字,这里M比G大,所有积蓄查找M的左子树,左子树的根节点关键字有:D、H。同样第一个比G大的是H。H的左孩子结点关键字是F、G。这样,G被找到了。返回。
图四
算法具体描述为:
bool SearchBTreeByValue(BTreeNode* q,NodeValueType k){
int i=;
while(i<q->keyNum&&q->nodeValues[i]<=k){
if (q->nodeValues[i]==k){
return true;//相等则返回q
}
i++;
}
if (!q->childs.size()){
return false;//没有找到直接返回NULL
}
return SearchBTreeByValue(q->childs[i],k);
}
B-树的增加(插入)操作
【插入操作要寻找到叶子节点插入】因为B-树中最多只能保存2T-1个关键字,如果当前的关键字个数已经达到2T-1,还需插入一个的话就需要分裂成两个结点。如下图,T=2.如果还需插入一个18.由于结点X已经有3个关键字了。已经full了。如果还需要插入18,就不符合条件了,就需要分裂成两个结点,同时增加一层。再进行插入。也可以插入完成后,再分裂。最终还是要分裂。
如果B-树没有满的话,直接插入就好。
图五
图六
BTreeNode* InsertNodeToBTreeByValue(BTreeNode* p,NodeValueType theValue){
if (SearchBTreeByValue(p,theValue)){
cout<<theValue<<"已经存在了,骚年!"<<endl;
return p;
}
while(p->childs.size()!=){//一直找到叶子结点
int i=;
while(i<p->keyNum&&p->nodeValues[i]<theValue){
i++;
}
p=p->childs[i];
}
p->nodeValues=InsertKeyToNodeByValue(p->nodeValues,theValue);//插入到当前叶子结点
p->keyNum++;
return adjustBTree(p);//使得最大不超过maxKey
}
B-树的删除操作
说实话,删除操作比增加操作复杂多了,这也是我的程序一直出现bug的地方。假设要在结点x中删除关键字k。
因为删除操作需要考虑删除后结点会少于T-1和树不平衡的情况。
删除操作主要考虑三种情况:
1)x是叶子结点,包含k
2)x是内部结点,包含k
3)x是内部结点,不包含k.
对于1),直接删除,同时更新keynum;
对于2),考虑k的左右孩子,
若有一个孩子的关键字个数大于T-1,若左孩子y,用最右边的关键字和k交换,同时递归删除delete(y,k);
若右孩子(z)数大于T-1,则递归删除delete(z,k);
若左右孩子都等于T-1,则需要合并左孩子,k,右孩子,同时删除右孩子。
对于3),y=x.child[k];若y的关键字个数大于T-1,则递归删除delete(y,k)
若y的关键字个数等于T-1,则寻找他的兄弟节点,兄弟节点若有大于T-1个数的,补一个过来。
若没有的话,就进行和其中兄弟节点合并,再递归delete.
BTreeNode* DeleteBTreeNode(BTreeNode* p,NodeValueType theValue){
int theValueOrderInNode=GetOrderInNodeByValue(p,theValue);//返回当前序号,theValueOrderInNode
//BTreeNode* w=p->parentNode;
if (theValueOrderInNode!=-&&p->childs.size()==){
p->nodeValues.erase(p->nodeValues.begin()+theValueOrderInNode,p->nodeValues.begin()+theValueOrderInNode+);
p->keyNum--;
return GetBTreeRoot(p);
}else if (theValueOrderInNode==-){//该结点中不存在theValue
int i=;
while(i<p->keyNum&&p->nodeValues[i]<theValue){
i++;//得到第I个孩子。
}
BTreeNode* y=p->childs[i];//找到i+1个孩子结点
if (y->keyNum>atLeastKeyNum){
return DeleteBTreeNode(y,theValue);
}else if(y->keyNum==atLeastKeyNum){
BTreeNode* leftSlibing=new BTreeNode(),*rightSlibing=new BTreeNode();//左右结点出现啦!
leftSlibing=NULL,rightSlibing=NULL;
if (i!=){//有左兄弟
leftSlibing=p->childs[i-];
}
if (i!=p->keyNum){//有右兄弟
rightSlibing=p->childs[i+];
}
if (leftSlibing!=NULL&&leftSlibing->keyNum>atLeastKeyNum){//左兄弟移动一个到y中
y->nodeValues.insert(y->nodeValues.begin(),p->nodeValues[i-]);
y->keyNum++;
p->nodeValues[i-]=leftSlibing->nodeValues[leftSlibing->keyNum-];
if (y->childs.size()!=){
y->childs.insert(y->childs.begin(),*(leftSlibing->childs.end()-));
leftSlibing->childs.erase(leftSlibing->childs.end()-,leftSlibing->childs.end());
}
leftSlibing->nodeValues.erase(leftSlibing->nodeValues.end()-,leftSlibing->nodeValues.end());
leftSlibing->keyNum--;
return DeleteBTreeNode(y,theValue);
}else if(rightSlibing!=NULL&&rightSlibing->keyNum>atLeastKeyNum){//右兄弟移动一个到y中
y->nodeValues.insert(y->nodeValues.end(),p->nodeValues[i]);
y->keyNum++;
if (y->childs.size()!=){
y->childs.insert(y->childs.end(),*(rightSlibing->childs.begin()));
rightSlibing->childs.erase(rightSlibing->childs.begin(),rightSlibing->childs.begin()+);
}
p->nodeValues[i]=rightSlibing->nodeValues[];
rightSlibing->nodeValues.erase(rightSlibing->nodeValues.begin(),rightSlibing->nodeValues.begin()+);
rightSlibing->keyNum--;
return DeleteBTreeNode(y,theValue);
}else{//合并成一个
if (leftSlibing!=NULL){//同左合并
leftSlibing->nodeValues.insert(leftSlibing->nodeValues.end(),p->nodeValues[i-]);
leftSlibing->keyNum++;
p->nodeValues.erase(p->nodeValues.begin()+i-,p->nodeValues.begin()+i);
MoveBTreeNode(leftSlibing,y);
p->childs.erase(p->childs.begin()+i-,p->childs.begin()+i);
p->keyNum--;
if (p->keyNum==){
leftSlibing->parentNode=NULL;
}
return DeleteBTreeNode(leftSlibing,theValue);
}else{//同右合并
y->nodeValues.insert(y->nodeValues.end(),p->nodeValues[i]);
y->keyNum++;
p->nodeValues.erase(p->nodeValues.begin()+i,p->nodeValues.begin()+i+);
MoveBTreeNode(y,rightSlibing);
p->childs.erase(p->childs.begin()+i+,p->childs.begin()+i+);
p->keyNum--;
if (p->keyNum==){
y->parentNode=NULL;
}
return DeleteBTreeNode(y,theValue);
}
}
}
}else if (theValueOrderInNode!=-){//在该结点中找到了。
BTreeNode* leftChild=GetPreChildByNodeValue(p,theValue);
BTreeNode* rightChild=GetSuccessByNodeValue(p,theValue);
if (leftChild!=NULL&&leftChild->keyNum>atLeastKeyNum){//左
p->nodeValues[theValueOrderInNode]=leftChild->nodeValues[leftChild->keyNum-];
leftChild->nodeValues[leftChild->keyNum-]=theValue;
//leftChild->parentNode=p;
return DeleteBTreeNode(leftChild,theValue);
}
else if (rightChild!=NULL&&rightChild->keyNum>atLeastKeyNum){//右
p->nodeValues[theValueOrderInNode]=rightChild->nodeValues[];
rightChild->nodeValues[]=theValue;
//rightChild->parentNode=p;
return DeleteBTreeNode(rightChild,theValue);
}
else if (leftChild!=NULL){//需要合并了
leftChild->nodeValues.insert(leftChild->nodeValues.end(),p->nodeValues[theValueOrderInNode]);
leftChild->keyNum++;
MoveBTreeNode(leftChild,rightChild);//从左移到右
p->nodeValues.erase(p->nodeValues.begin()+theValueOrderInNode,p->nodeValues.begin()+theValueOrderInNode+);
p->childs.erase(p->childs.begin()+theValueOrderInNode+,p->childs.begin()+theValueOrderInNode+);
p->keyNum--;
if (p->keyNum==){
leftChild->parentNode=NULL;
}
return DeleteBTreeNode(leftChild,theValue);
}
}
return NULL;
}
操作数据:用106,103,109,130,145,165,42,60,136,107,108对B-树进行初始化。然后再进行查找删除操作。
图七
参考文献:http://zgking.com:8080/home/donghui/publications/books/dshandbook_BTree.pdf
http://webdocs.cs.ualberta.ca/~holte/T26/del-b-tree.html
http://www.cs.nott.ac.uk/~nza/G52ADS/btrees2.pdf
版权所有,欢迎转载,但是转载请注明出处:潇一
数据结构之B-树,你每天都在用的,源码发布!的更多相关文章
- [数据结构1.2-线性表] 动态数组ArrayList(.NET源码学习)
[数据结构1.2-线性表] 动态数组ArrayList(.NET源码学习) 在C#中,存在常见的九种集合类型:动态数组ArrayList.列表List.排序列表SortedList.哈希表HashTa ...
- Hashtable数据存储结构-遍历规则,Hash类型的复杂度为啥都是O(1)-源码分析
Hashtable 是一个很常见的数据结构类型,前段时间阿里的面试官说只要搞懂了HashTable,hashMap,HashSet,treeMap,treeSet这几个数据结构,阿里的数据结构面试没问 ...
- Android中将xml布局文件转化为View树的过程分析(下)-- LayoutInflater源码分析
在Android开发中为了inflate一个布局文件,大体有2种方式,如下所示: // 1. get a instance of LayoutInflater, then do whatever yo ...
- PHP 源码学习 | 变量类型数据结构
前段时间因为项目需要,研究了一下在 Windows 系统下进行 PHP 扩展的开发,对于 PHP 扩展的开发并不是一件容易的事情(话又说回来了,会者不难,难者不会,关键是自己不会).我当时的需求,主要 ...
- [数据结构-线性表1.2] 链表与 LinkedList<T>(.NET 源码学习)
[数据结构-线性表1.2] 链表与 LinkedList<T> [注:本篇文章源码内容较少,分析度较浅,请酌情选择阅读] 关键词:链表(数据结构) C#中的链表(源码) 可空类 ...
- Java程序员阅读源码的小技巧,原来大牛都是这样读的,赶紧看看!
今天介跟大家分享一下我平时阅读源码的几个小技巧,对于阅读java中间件如Spring.Dubbo等框架源码的同学有一定帮助. 本文基于Eclipse IDE,我们每天都使用的IDE其实提供了很多强大的 ...
- D&F学数据结构系列——B树(B-树和B+树)介绍
B树 定义:一棵B树T是具有如下性质的有根树: 1)每个节点X有以下域: a)n[x],当前存储在X节点中的关键字数, b)n[x]个关键字本身,以非降序存放,因此key1[x]<=key2[x ...
- Go 数据结构--二分查找树
Go 数据结构--二分查找树 今天开始一个Go实现常见数据结构的系列吧.有时间会更新其他数据结构. 一些概念 二叉树:二叉树是每个节点最多有两个子树的树结构. 完全二叉树:若设二叉树的高度为h,除第 ...
- 数据结构之B树、B+树(一)
B-树 什么是B-树? B树是一种查找树,我们知道,这一类树(比如二叉搜索树,红黑树等等)最初生成的目的都是为了解决某种系统中,查找效率低的问题.B树也是如此,它最初启发于二叉搜索树,二叉搜索树的特点 ...
随机推荐
- [06] JavaScript 类型
下面对知识点总结: 1.类型分类 a.原始类型:number, string, boolean, null, undefined b.对象类型:除了原始类型都是(例如:object,array, fu ...
- jsonp应用
1.服务端jsonp格式数据 如客户想访问 : http://www.runoob.com/try/ajax/jsonp.php?jsonp=callbackFunction. 假设客户期望返回JSO ...
- WEB-INF 有关的目录路径问题总结
1.资源文件只能放在WebContent下面,如 CSS,JS,image等.放在WEB-INF下引用不了. 2.页面放在WEB-INF目录下面,这样可以限制访问,提高安全性.如JSP,html 3. ...
- Spring学习之路——单例模式和多例模式
在Spring中,bean可以被定义为两种模式:prototype(多例)和singleton(单例) singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实 ...
- 【洛谷 P4886】 快递员 (点分治)
这题因为一些小细节还是\(debug\)了很久...不过我第一次用脚本对拍,不亏. 先随便找一个点作为根,算出答案,即所有点对到这个点的距离和的最大值,并记录所有距离最大的点对.如果这个点在任意一个距 ...
- MySQL中EXISTS的用法
比如在Northwind数据库中有一个查询为 SELECT c.CustomerId,CompanyName FROM Customers c WHERE EXISTS( SELECT OrderID ...
- Linux下Tomcat开机自动启动
linux下tomcat开机自动启动有两种方法,一种是简单,一种是复杂而又专业的,使用shell脚本要实现,我们一般推荐shell脚本启动方式.下面我们分别介绍这两种方法. 1.shell脚本启动 众 ...
- selenium自动化添加日志
于logging日志的介绍,主要有两大功能,一个是控制台的输出,一个是保存到本地文件 先封装logging模块,保存到common文件夹命名为logger.py,以便于调用,直接上代码 filenam ...
- js判断对象为空
http://www.jb51.net/article/42713.htm var isEmptyValue = function(value) { var type; if(value == nul ...
- 6.memcached缓存系统
1.memcached的安装和参数 memcached缓存系统一般还是部署在linux服务器上,所以这里只介绍linux上memcache的安装 首先切换到root用户,然后apt-get insta ...