数据结构之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-树的数据结构代码化之后如下:

 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

 

      版权所有,欢迎转载,但是转载请注明出处:潇一

 

 

posted @ 2014-05-04 19:59  白来了123  阅读(2417)  评论(8编辑  收藏  举报