B树、B+树数据结构及操作

B树的定义:

B树(B-tree)是一种树状数据结构,它能够存储数据、对其进行排序并允许以O(log n)的时间复杂度运行进行查找、顺序读取、插入和删除的数据结构。

B树结构如下:

《B树、B+树数据结构及操作》

其中,m是B树的阶,m>=3,par是指向父节点指针。

K1,K2…Kn是从小到大顺序排列的关键字。n是变化的:

(1),对于非树根节点:m/2-1 <= n <=  m-1

(2),对于根节点:1<= n <= m-1

P0,P1,P2…Pn 为n+1个指针,用于指向n+1颗子树.其中,P0指向的关键字都小于K1,Pn指向的关键字都大于Kn,Pi(1<= i <=n-1 )指向的关键字均大于Ki小于K[i+1]

其他特性:

(1),根节点最少有两颗子树,最多有m颗子树

(2),非根节点最少有m/2颗子树,最多有m颗子树.

数据结构:

#define KeyType int
#define Pointer int
const int m=10;   //树的阶

struct MBNode{
	
    int keynum;              //关键字个数
    MBNode *parent;      //指向父节点
    KeyType key[m+1];   //关键字个数
    MBNode *ptr[m+1];    //指向子节点
    Pointer recptr[m+1];     //关键字对应的存储位置
    
}
//key和recptr都是从1开始,故而0位置未用

1, 查找:

//从树根MT的b树上查找关键字为K的记录位置
int SearchMBTree(MBNode *MT,KeyType k)
{
    int i ;
    MBNode *p=MT;

	
    while(p != NULL)   //从树根处向下查找
    {
        i=1;                          //i表示关键字序号初始值1
        while(k > p->key[i])//顺序比较
        	i++;
        if(k == p->key[i])         //如果找到
	     return p->recptr[i];
        else
	     p= p->ptr[i-1];        //查找下一个节点
    }
    return -1;
}

查找比较简单,还有一些查找时间复杂度的计算就不多做介绍.

2,插入:

新结点通过搜索找到对应的结点进行插入,该节点插入一个新节点后该节点的关键字个数为n分为下面几种情况:
(1)如果该结点的关键字个数 <= m-1个,那么直接插入即可;
(2)如果该结点的关键字个数 > m-1个,那么根据B树的性质显然无法满足,需要将其进行分裂。
分裂的规则是该结点分成两半,将中间的关键字进行提升,加入到父亲结点中,但是这又可能存在父亲结点也满员的情况,则不得不向上进行回溯,甚至是要对根结点进行分裂,那么整棵树都加了一层.

具体过程:

(1),生成一个新节点a’

(2),将a节点中的原有信息:m,P0,(K1,P1),(K2,P2),…,(Km,Pm),除K[m/2]之外分为前后两个部分,分别存于a和a’节点:

     a 节点:

                 [m/2]-1,P0,(K1,P1),(K2,P2),…,(K[m/2-1],P[m/2-1])

     a’ 节点:

                 m-[m/2],P[m/2],(K[m/2+1],P[m/2+1]),…,(Km,Pm)

     其中a节点中含有[m/2]-1个索引,a’ 中含有m-[m/2]个索引项,每个索引项包含一个关键字Ki,该关键字所对应的记录存储位置Ri和一个子树指针Pi.

(3),将关键字K[m/2]和指向a’ 节点的指针(假定用P表示)作为新节点a’ 的索引项(K[m/2],P)插入到a节点在前驱节点(即父亲节点)中的索引项是的后面(特别的.若a节点是由前驱节点中的P0指针指向的,则插入到K1和P1的位置上).

当a的前驱节点被插入一个索引项后,其关键字个数又有可能超过m-1,若超过又使得该节点分裂成两个节点,分裂过程如上.如下:

《B树、B+树数据结构及操作》

《B树、B+树数据结构及操作》

《B树、B+树数据结构及操作》

《B树、B+树数据结构及操作》

插入代码:

//向树根MT的树种插入(k,num,NULL)
bool InsertMBTree(MBTree *MT,KeyType k,int num)
{

    //当树为空时
    if(MT == NULL)
    {
    	  MT= new MBNode;
         MT->keynum =1;
	  MT->parent = NULL;
	  MT->key[1]=k;
	  MT->key[2]=MaxKey;
	  MT->recptr[1]=num;
	  MT->ptr[0]=MT->ptr[1]=NULL;
	  return true;
	  
    }

    //从树中查到到插入的位置
    int i;
    MBNode *xp=MT,*p=NULL;
    while(xp != NULL)
    {
         i=1;
	  while(k > xp->key[i])
  	      i++;
	  if(k == xp->key[i])
	  {
	      return false;   //key 已经存在返回异常
	  }
	  else
	  {
	      p=xp;
	      xp=xp->ptr[i];   //找下一层
	  }
    }

    //准备向p中插入索引项
    //此时k < p->key[i]
    MBNode *ap=NULL;
    while(1)
    {
        int j,c;
	 for (j=p->keynum;j >= i;j--)
	 {
	     p->key[j+1]=p->key[j];
	     p->recptr[j+1]=p->recptr[j];
	     p->ptr[j+1]=p->ptr[j];
	 }
	 //把一个插入索引项(k,num,ap)放入p节点的i下标位置
	 p->key[i]=k;
	 p->recptr[i]=num;
	 p->ptr[i]=ap;
	 p->keynum++;
	 //若插入后节点中关键字个数不超过所允许的最大值,则完成插入
	 if(p->keynum <= m-1)
	 {
	     p->key[p->keynum +1]=MaxKey;
	     return true;
	 }

	 //以下为节点分裂的情况
	 //计算出m/2向上取整值
	 c=(m%2 ? (m+1)/2;m/2);
	 //建立新节点该节点含有m-c个索引值
	 ap=new MBNode;
	 ap->keynum=m-c;
	 ap->parent=p->parent;

        //复制关键字和记录
	 for(j=1;j<= ap->keynum; j++)
	 {
	     ap->key[j]=p->key[j+c];
	     ap->recptr[j]=p->recptr[j+c];
	 }

	  //复制指针
	 for(j=0;j<= ap->keynum; j++)
	 {
	     ap->ptr[j]=p->ptr[j+c];
	     if(ap->ptr[j] != NULL)
	     {
	         ap->ptr[j]->parent=ap;
	     }
	 }
	 //最大值放入所有关键字之后
	 ap->key[m-c+1]=MaxKey;

	 //修改p节点中的关键字个数
	 p->keynum=c-1;

	 //建立新的待向双亲节点插入的索引项(k,num,ap)
	 k=p->key[c];
	 num=p->recptr[c];

	 //p节点所有关键字后放入最大键值
	 p->key[c]=MaxKey;

	 //如果p的父节点是根节点,则建立新的树根节点
	 if(p->parent == NULL)
	 {
	     MT=new MBNode;
	     MT->keynum=1;
	     MT->parent=NULL;
            MT->key[1]=k;
	     MT->key[2]=MaxKey;
	     MT->recptr[1]=num;
	     MT->ptr[0]=p;
	     MT->ptr[1]=ap;
	     p->parent=ap->parent=MT;
	     return true;
	 }

	 //如果p的父节点不是根节点
	 p=p->parent;
	 i=1;
	 while(k > p->key[i])
	 {
	     i++;
	 }
    }
}

3,删除:

首先从根节点查找,然后再分情况进行删除。若被删除的关键字在叶子节点中则直接从该叶子结点中删除之,若被删除的关键字在非叶子结点中,则首先要将被删除的关键字同他的中序前驱关键字(即它的左边指针所指子树的最右下叶子节点中的最大关键字)或中序后继关键字(即他的右边指针所指子树的最左下叶子节点中的最小关键字)进行对调(连同对应记录的存储位置一起对调),然后再从对应的结点中删除之。

从叶子结点中删除一个关键字后,使得该节点的关键字个数减少1,此时分3种情况分析:

(1)、若删除后该节点的关键字个数 n >= m/2-1,则删除完成。

(2)、若删除后该节点的关键字个数 n < m/2-1,而他的左兄弟(或者右兄弟)节点中的关键字个数n > m/2-1,则首先将双亲节点中的指向该节点的左边(或者右边)一个关键字下移至该节点中,接着将它的左兄弟(或者右兄弟)结点中的最大关键字(或者最小关键字)上移至他们的双亲结点中刚空出来的位置,然后再将左兄弟(或者右兄弟)节点中的Pn指针(或者P0指针)赋值给该节点的P0(或者Pn)指针。

(3)、若删除后该节点的关键字个数n < m/2-1,同时它的左兄弟和右兄弟节点中的关键字个数均等于m/2-1在这种情况下就必须进行节点合并了。即将该节点的剩余关键字和指针连同双亲结点中指向该节点指针的左边(或者右边)一个关键字一起合并到左兄弟(或者右兄弟)结点中,然后回收该节点。

其过程如下:

《B树、B+树数据结构及操作》

《B树、B+树数据结构及操作》

《B树、B+树数据结构及操作》

#define  T 3 
typedef struct B_Tree_Node  //b树节点定义 
{  
    int n;                         
    int *keys;                      
    bool isLeaf;                      
    struct B_Tree_Node **child ;      
    struct B_Tree_Node *p;           
}B_Tree_Node, *p_B_Tree_Node;  


B_Tree_Node * searchNode(B_Tree_Node *curNode, int k, int &index)  //关键字k的查找,返回查找信息 
{  
    int i = 0;  
    while(i<=curNode->n && k >curNode->keys[i])   
        i++;  
	
    if(i<curNode->n && k == curNode->keys[i])  //找到了k  
    {  
        index = i;  
        return curNode;  
    }  
	
    if(curNode->isLeaf) //如果该结点是叶子结点,则k不存在  
        return NULL;  
	
    searchNode(curNode->child[i],k,index);  

}


B_Tree_Node *alloact_Node()       //b树的初始化 
{  
    B_Tree_Node *newNode = new B_Tree_Node;  
    newNode->n = 0;  
    newNode->isLeaf = true;  
    newNode->keys = new int[2*T-1];  
    newNode->child = new p_B_Tree_Node[2*T];  
    newNode->p = NULL;  
    for(int i=0;i<2*T;i++)  
        newNode->child[i] = NULL;  
    return newNode;  
}  


void BTree_delete_key(B_Tree_Node *subNode, int k)  //删除关键字k,涉及合并 
{  
    int index = 0;  
    B_Tree_Node *deleteNode = NULL;  
	
    if((deleteNode = searchNode(subNode,k,index)) == NULL)  
        return;  
	
    int keyIndex = -1;  
    for(int i=0;i<subNode->n;i++)  
    {  
        if(k == subNode->keys[i])  
        {  
            keyIndex = i;  
            break;  
        }  
    }  
    //如果在当前结点,且当前结点为叶子结点,则直接删除
    if(keyIndex != -1 && subNode->isLeaf)   
    {  
        for(int i=keyIndex;i<subNode->n-1;i++)  
        {  
            subNode->keys[i] = subNode->keys[i+1];  
        }  
        (subNode->n)--;  
    }  
    else if(keyIndex != -1 && subNode->isLeaf!= true)   //如果在当前结点中,且当前结点不为叶子结点  
    {  
        B_Tree_Node *processorNode = subNode->child[keyIndex];  
        B_Tree_Node *succssorNode = subNode->child[keyIndex+1];  

        if(processorNode->n >= T)   //如果小于k的孩子结点关键字数大于T  
        {  
            int k1 = processorNode->keys[processorNode->n-1];  
            subNode->keys[keyIndex] = k1;  
            BTree_delete_key(processorNode,k1);  
        }  

        else if(succssorNode->n >=T)   //如果大于k的孩子结点关键字数大于T 
        {  
            int k1 = succssorNode->keys[0];  
            subNode->keys[keyIndex] = k1;  
            BTree_delete_key(succssorNode,k1);  
        }  

        else   //如果两个孩子结点关键字数均不大于T,则将k与右孩子结点的关键字归并到左孩子中  
        {  
            for(int j=0;j<T-1;j++)  
            {  
                processorNode->keys[processorNode->n] = k;  
                processorNode->keys[processorNode->n+1+j] = succssorNode->keys[j];  
            }  

            processorNode->n = 2*T -1 ;    
            if(!processorNode->isLeaf)  
            {  
                for(int j=0;j<T;j++)  
                {  
                    processorNode->child[T+j] = succssorNode->child[j];  
                }  
            }  

            for(int j = keyIndex;j<subNode->n-1;j++)   //修改subNode中的key值  
            {  
                subNode->keys[j] = subNode->keys[j+1];  
            }  
            subNode->n = subNode->n - 1;  
            delete succssorNode;  
            BTree_delete_key(processorNode,k);  


        }  

    }  
    else if(keyIndex == -1) //不在当前结点中  
    {  
        int childIndex = 0;  
        B_Tree_Node *deleteNode = NULL;  
        for(int j = 0;j<subNode->n;j++)     //寻找合适的子孩子,以该子孩子为根的树包含k  
        {  
            if(k<subNode->keys[j])  
            {  
                childIndex = j;  
                deleteNode = subNode->child[j];  
                break;  
            }  
        }  
        if(deleteNode->n <= T-1)   //如果该子孩子的关键字数小于T,考虑那两种情况  
        {  

            B_Tree_Node *LeftNode = subNode->child[childIndex-1];   //deleteNode的左兄弟结点  

            B_Tree_Node *RightNode = subNode->child[childIndex+1];   //deleteNode的右兄弟结点 
//如果左兄弟结点关键字数大于T,将父结点中的第childIndex-1个元素送给deleteNode,将Left中的最大元素送给父结点, 
            if(childIndex>=1 && LeftNode->n >= T)    
            {  
                for(int i = deleteNode->n;i>0;i--)  
                {  
                    deleteNode->keys[i] = deleteNode->keys[i-1];  
                }  
                deleteNode->keys[0] = subNode->keys[childIndex];  
                subNode->keys[childIndex] = LeftNode->keys[LeftNode->n - 1];  
                (LeftNode->n)--;  
                (deleteNode->n)++;  
                BTree_delete_key(deleteNode,k);  
            } 
//如果右兄弟关键字大于T,将父结点中的第childIndex个元素送给deleteNode,将Right中的最小元素送给父结点, 
            else if(childIndex<subNode->n && RightNode->n >= T)               
            {  
                deleteNode->keys[deleteNode->n] = subNode->keys[childIndex];  
                subNode->keys[childIndex] = RightNode->keys[0];  
                for(int i=0;i<RightNode->n-1;i++)  
                    RightNode[i] = RightNode[i+1];  
                (RightNode->n)--;  
                (deleteNode->n)++;  
                BTree_delete_key(deleteNode,k);  
            }  
 //如果左兄弟和右兄弟的关键字数均不在于T,则将左兄弟或右兄弟与其合并  
            else   
            {  
                if(childIndex>=1)//左兄弟存在,合并  
                {  
                    //将keys合并  
                    for(int i=0;i<deleteNode->n;i++)  
                    {  
                        LeftNode->keys[LeftNode->n+i] = deleteNode->keys[i];  
                    }  
                    //如果非叶子结点,则将叶子也合并  
                    if(!deleteNode->isLeaf)  
                    {  
                        for(int i=0;i<deleteNode->n+1;i++)  
                        {  
                            LeftNode->child[LeftNode->n+1+i] = deleteNode->child[i];  
                        }  

                    }  
                    LeftNode->n = LeftNode->n + deleteNode->n;  

                    //调整subNode的子节点  
                    for(int i = childIndex;i<subNode->n;i++)  
                    {  
                        subNode->child[i] = subNode->child[i+1];  
                    }  
                    BTree_delete_key(LeftNode,k);  
                }  
                else //合并它和右兄弟  
                {  
                    //将keys合并  
                    for(int i=0;i<RightNode->n;i++)  
                    {  
                        deleteNode->keys[i+deleteNode->n] = RightNode->keys[i];  
                    }  
                    //如果非叶子结点,则将叶子合并  
                    if(!deleteNode->isLeaf)  
                    {  
                        for(int i = 0;i<RightNode->n+1;i++)  
                        {  
                            deleteNode->child[deleteNode->n + 1 + i] = RightNode->child[i];  
                        }  
                    }  
                    deleteNode->n = deleteNode->n + RightNode->n;  

                    //调整subNode的子节点  
                    for(int i = childIndex+1;i<subNode->n;i++)  
                    {  
                        subNode->child[i] = subNode->child[i+1];  
                    }  
                    BTree_delete_key(deleteNode,k);  
                }  
            }  

        }  
        BTree_delete_key(deleteNode,k);  
    }  
}  

B+

       B+树是B树的变体,也是一种多路搜索树:

       1.其定义基本与B-树同,除了:

       2.非叶子结点的子树指针与关键字个数相同;

       3.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B树是开区间);

       5.为所有叶子结点增加一个链指针;

       6.所有关键字都在叶子结点出现;

B+
的特性:

       1.所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好

是有序的;

       2.不可能在非叶子结点命中;

       3.非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储

(关键字)数据的数据层;

       4.更适合文件索引系统;

    原文作者:B树
    原文地址: https://blog.csdn.net/cyq6239075/article/details/78936182
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞