数据结构之B-树,你每天都在用的,源码发布!

[QQ群:
189191838,对算法和C++感兴趣可以进来]

五一前就筹划着写下这篇文章,但是迫于自己从来没有实现过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-树的数据结构代码化之后如下:


 1 struct BTreeNode{
2 int keyNum;//关键字个数。
3 BTreeNode* parentNode;//父结点指针
4 vector<NodeValueType> nodeValues;
5 vector<BTreeNode*> childs;
6 BTreeNode(){
7 keyNum=0;
8 parentNode=NULL;
9 }
10 };

对于B-树而言最常用的就是增删改查了。改可以认为是删,然后增。所以可以理解成主要操作有增、删、查!下面分三部分详解他们。

B-树的查询操作:

   
 
B-树的查询可以从根节点开始查找,根节点没有查到,再查找某一个孩子结点。

因为B-树结点都有很多孩子节点,查找哪个孩子结点就显得特别重要了,总不能乱查吧。它应该是查找第一个比关键字大的左结点,如果没有一个比该关键字大的关键字,则查找最右孩子。如此递归。直到查到叶子结点,如果叶子结点也没有找到,那么B-树中不存在该关键字。

   
比如对于图三,查找关键字G。
首先在根节点中查找,根节点只有M一个关键字,显然不符合条件。查找到第一个大于G的关键字,这里M比G大,所有积蓄查找M的左子树,左子树的根节点关键字有:D、H。同样第一个比G大的是H。H的左孩子结点关键字是F、G。这样,G被找到了。返回。

图四

算法具体描述为:


 1 bool SearchBTreeByValue(BTreeNode* q,NodeValueType k){
2 int i=0;
3 while(i<q->keyNum&&q->nodeValues[i]<=k){
4 if (q->nodeValues[i]==k){
5 return true;//相等则返回q
6 }
7 i++;
8 }
9 if (!q->childs.size()){
10 return false;//没有找到直接返回NULL
11 }
12 return SearchBTreeByValue(q->childs[i],k);
13 }

B-树的增加(插入)操作

【插入操作要寻找到叶子节点插入】因为B-树中最多只能保存2T-1个关键字,如果当前的关键字个数已经达到2T-1,还需插入一个的话就需要分裂成两个结点。如下图,T=2.如果还需插入一个18.由于结点X已经有3个关键字了。已经full了。如果还需要插入18,就不符合条件了,就需要分裂成两个结点,同时增加一层。再进行插入。也可以插入完成后,再分裂。最终还是要分裂。

如果B-树没有满的话,直接插入就好。

图五

图六 


 1 BTreeNode* InsertNodeToBTreeByValue(BTreeNode* p,NodeValueType theValue){
2 if (SearchBTreeByValue(p,theValue)){
3 cout<<theValue<<"已经存在了,骚年!"<<endl;
4 return p;
5 }
6 while(p->childs.size()!=0){//一直找到叶子结点
7 int i=0;
8 while(i<p->keyNum&&p->nodeValues[i]<theValue){
9 i++;
10 }
11 p=p->childs[i];
12 }
13 p->nodeValues=InsertKeyToNodeByValue(p->nodeValues,theValue);//插入到当前叶子结点
14 p->keyNum++;
15 return adjustBTree(p);//使得最大不超过maxKey
16 }

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.


  1 BTreeNode* DeleteBTreeNode(BTreeNode* p,NodeValueType theValue){
2 int theValueOrderInNode=GetOrderInNodeByValue(p,theValue);//返回当前序号,theValueOrderInNode
3 //BTreeNode* w=p->parentNode;
4 if (theValueOrderInNode!=-1&&p->childs.size()==0){
5 p->nodeValues.erase(p->nodeValues.begin()+theValueOrderInNode,p->nodeValues.begin()+theValueOrderInNode+1);
6 p->keyNum--;
7 return GetBTreeRoot(p);
8 }else if (theValueOrderInNode==-1){//该结点中不存在theValue
9 int i=0;
10 while(i<p->keyNum&&p->nodeValues[i]<theValue){
11 i++;//得到第I个孩子。
12 }
13 BTreeNode* y=p->childs[i];//找到i+1个孩子结点
14 if (y->keyNum>atLeastKeyNum){
15 return DeleteBTreeNode(y,theValue);
16 }else if(y->keyNum==atLeastKeyNum){
17 BTreeNode* leftSlibing=new BTreeNode(),*rightSlibing=new BTreeNode();//左右结点出现啦!
18 leftSlibing=NULL,rightSlibing=NULL;
19 if (i!=0){//有左兄弟
20 leftSlibing=p->childs[i-1];
21 }
22 if (i!=p->keyNum){//有右兄弟
23 rightSlibing=p->childs[i+1];
24 }
25 if (leftSlibing!=NULL&&leftSlibing->keyNum>atLeastKeyNum){//左兄弟移动一个到y中
26 y->nodeValues.insert(y->nodeValues.begin(),p->nodeValues[i-1]);
27 y->keyNum++;
28 p->nodeValues[i-1]=leftSlibing->nodeValues[leftSlibing->keyNum-1];
29 if (y->childs.size()!=0){
30 y->childs.insert(y->childs.begin(),*(leftSlibing->childs.end()-1));
31 leftSlibing->childs.erase(leftSlibing->childs.end()-1,leftSlibing->childs.end());
32 }
33 leftSlibing->nodeValues.erase(leftSlibing->nodeValues.end()-1,leftSlibing->nodeValues.end());
34 leftSlibing->keyNum--;
35 return DeleteBTreeNode(y,theValue);
36 }else if(rightSlibing!=NULL&&rightSlibing->keyNum>atLeastKeyNum){//右兄弟移动一个到y中
37 y->nodeValues.insert(y->nodeValues.end(),p->nodeValues[i]);
38 y->keyNum++;
39 if (y->childs.size()!=0){
40 y->childs.insert(y->childs.end(),*(rightSlibing->childs.begin()));
41 rightSlibing->childs.erase(rightSlibing->childs.begin(),rightSlibing->childs.begin()+1);
42 }
43 p->nodeValues[i]=rightSlibing->nodeValues[0];
44 rightSlibing->nodeValues.erase(rightSlibing->nodeValues.begin(),rightSlibing->nodeValues.begin()+1);
45 rightSlibing->keyNum--;
46 return DeleteBTreeNode(y,theValue);
47 }else{//合并成一个
48 if (leftSlibing!=NULL){//同左合并
49 leftSlibing->nodeValues.insert(leftSlibing->nodeValues.end(),p->nodeValues[i-1]);
50 leftSlibing->keyNum++;
51 p->nodeValues.erase(p->nodeValues.begin()+i-1,p->nodeValues.begin()+i);
52 MoveBTreeNode(leftSlibing,y);
53 p->childs.erase(p->childs.begin()+i-1,p->childs.begin()+i);
54 p->keyNum--;
55 if (p->keyNum==0){
56 leftSlibing->parentNode=NULL;
57 }
58 return DeleteBTreeNode(leftSlibing,theValue);
59 }else{//同右合并
60 y->nodeValues.insert(y->nodeValues.end(),p->nodeValues[i]);
61 y->keyNum++;
62 p->nodeValues.erase(p->nodeValues.begin()+i,p->nodeValues.begin()+i+1);
63 MoveBTreeNode(y,rightSlibing);
64 p->childs.erase(p->childs.begin()+i+1,p->childs.begin()+i+2);
65 p->keyNum--;
66 if (p->keyNum==0){
67 y->parentNode=NULL;
68 }
69 return DeleteBTreeNode(y,theValue);
70 }
71 }
72 }
73 }else if (theValueOrderInNode!=-1){//在该结点中找到了。
74 BTreeNode* leftChild=GetPreChildByNodeValue(p,theValue);
75 BTreeNode* rightChild=GetSuccessByNodeValue(p,theValue);
76 if (leftChild!=NULL&&leftChild->keyNum>atLeastKeyNum){//左
77 p->nodeValues[theValueOrderInNode]=leftChild->nodeValues[leftChild->keyNum-1];
78 leftChild->nodeValues[leftChild->keyNum-1]=theValue;
79 //leftChild->parentNode=p;
80 return DeleteBTreeNode(leftChild,theValue);
81 }
82 else if (rightChild!=NULL&&rightChild->keyNum>atLeastKeyNum){//右
83 p->nodeValues[theValueOrderInNode]=rightChild->nodeValues[0];
84 rightChild->nodeValues[0]=theValue;
85 //rightChild->parentNode=p;
86 return DeleteBTreeNode(rightChild,theValue);
87 }
88 else if (leftChild!=NULL){//需要合并了
89 leftChild->nodeValues.insert(leftChild->nodeValues.end(),p->nodeValues[theValueOrderInNode]);
90 leftChild->keyNum++;
91 MoveBTreeNode(leftChild,rightChild);//从左移到右
92 p->nodeValues.erase(p->nodeValues.begin()+theValueOrderInNode,p->nodeValues.begin()+theValueOrderInNode+1);
93 p->childs.erase(p->childs.begin()+theValueOrderInNode+1,p->childs.begin()+theValueOrderInNode+2);
94 p->keyNum--;
95 if (p->keyNum==0){
96 leftChild->parentNode=NULL;
97 }
98 return DeleteBTreeNode(leftChild,theValue);
99 }
100 }
101 return NULL;
102 }

操作数据:用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-树,你每天都在用的,源码发布!,布布扣,bubuko.com

时间: 2024-08-06 11:42:00

数据结构之B-树,你每天都在用的,源码发布!的相关文章

Android中将xml布局文件转化为View树的过程分析(下)-- LayoutInflater源码分析

在Android开发中为了inflate一个布局文件,大体有2种方式,如下所示: // 1. get a instance of LayoutInflater, then do whatever you want LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // 2. you're in some View class, then jus

为什么C/C++程序员都要阅读Redis源码之:Redis学习事件驱动设计

为什么我说C/C++程序员都要阅读Redis源码 主要原因就是『简洁』.如果你用源码编译过Redis,你会发现十分轻快,一步到位.其他语言的开发者可能不会了解这种痛,作为C/C++程序员,如果你源码编译安装过Nginx/Grpc/Thrift/Boost等开源产品,你会发现有很多依赖,而依赖本身又有依赖,十分痛苦.通常半天一天就耗进去了.由衷地羡慕 npm/maven/pip/composer/...这些包管理器.而Redis则给人惊喜,一行make了此残生. 除了安装过程简洁,代码也十分简洁.

每天都爱笑应用安卓源码

本应用为娱乐类应用,汇集了各种笑话集锦.内涵段子及搞笑图片,使得用户感受到快乐,轻松愉悦,开心每一天.应用内容实时更新,有源码你可以学到项目漂亮框架,还可以学到如何从网络扒取数据信息.不多说,直接上图:<ignore_js_op>  <ignore_js_op> <ignore_js_op><ignore_js_op> <ignore_js_op> 详细说明:http://android.662p.com/thread-6053-1-1.html

PHP 源码学习 | 变量类型数据结构

前段时间因为项目需要,研究了一下在 Windows 系统下进行 PHP 扩展的开发,对于 PHP 扩展的开发并不是一件容易的事情(话又说回来了,会者不难,难者不会,关键是自己不会).我当时的需求,主要是通过 PHP 扩展来加载 DLL 文件,并调用 DLL 中的导出函数.由于以前有一些 Win32 开发的基础,因此这个需求相对还不算太复杂. 开发 PHP 扩展比较难,我个人认为有两方面的原因,一方面需要了解操作系统相关的开发,毕竟 PHP 扩展不是 .so 文件就是 .dll 文件,另一方面需要

基于知识树的多课程网络教学平台最新版源码 联系QQ:847129860

一.更新记录 1.更新日期:2015-07-08 12:00:00 2.更新内容: (1)添加测试迁移功能,可将以前的测试项目用于其他班级,并重新指定测试名称.时间等.(2)学生考试页面改成每次显示一道题,并添加了试卷检查功能.(3)修改页面弹出对话框影响页面布局的问题.(4)修正bug:在修改题目时不能上传文件的问题.(5)数据库中删除了一些无用的表.视图.字段.提供给老用户一个更新数据库的sql语句文本文件. 一.源码描述      这是一款基于知识树的多课程网络教学平台源码,支持多个课程,

【经典数据结构】B树与B+树

本文转载自:http://www.cnblogs.com/yangecnu/p/Introduce-B-Tree-and-B-Plus-Tree.html 维基百科对B树的定义为“在计算机科学中,B树(B-tree)是一种树状数据结构,它能够存储数据.对其进行排序并允许以O(log n)的时间复杂度运行进行查找.顺序读取.插入和删除的数据结构.B树,概括来说是一个节点可以拥有多于2个子节点的二叉查找树.与自平衡二叉查找树不同,B-树为系统最优化大块数据的读和写操作.B-tree算法减少定位记录时

数据结构之AVL树

说明:本文仅供学习交流,转载请标明出处,欢迎转载! 在前面的博文中,我们已经介绍了数据结构之二分查找树的相关知识,二分查找的提出主要是为了提高数据的查找效率.同一个元素集合可以对应不同的二分查找树BST,二分查找树的形态依赖于元素的插入顺序.同时我们也已经知道,如果将一个有序的数据集依次插入到二查找树中,此时二分查找树将退化为线性表,此时查找的时间复杂度为o(n).为了防止这一问题的出现,便有了平衡二叉树的存在价值.平衡二叉树从根本上将是为了防止出现斜二叉树的出现,从而进一步提高元素的查找效率,

D&amp;F学数据结构系列——B树(B-树和B+树)介绍

B树 定义:一棵B树T是具有如下性质的有根树: 1)每个节点X有以下域: a)n[x],当前存储在X节点中的关键字数, b)n[x]个关键字本身,以非降序存放,因此key1[x]<=key2[x]<=...<=keyn[x][x], c)leaf[x],是一个布尔值,如果x是叶子的话,则它为TRUE,如果x为一个内节点,则为FALSE. 2)每个内节点包含n[x]+1个指向其子女的指针c1[x],c2[x],...,cn[x]+1[x].叶节点没有子女,故它们的ci域无意义. 3)各关键

《数据结构》线段树入门(二)

今天继续介绍——线段树之延迟标记 接上期<数据结构>线段树入门(一):http://www.cnblogs.com/shadowland/p/5870339.html 在上期介绍了线段树的最基本内容(线段树单点修改,区间查询),这次将介绍:区间修改,区间查询. Question: 给你N个数,有两种操作: 1:给区间[a,b]的所有数增加X 2:询问区间[a,b]的数的和. 输入描述: 第一行一个正整数n,接下来n行n个整数,再接下来一个正整数Q,每行表示操作的个数,如果第一数是1,后接3个正