数据结构与算法分析-C++描述 第4章 B树

B树的性质:
    1)树中每个结点最多含有m棵子树; 
    2)若根结点不是叶子结点,则至少有2个子树; 
    3)除根结点之外的所有非终端结点至少有[m/2]棵子树; 
    4)每个非终端结点中包含信息:(n, A_{0}, K_{1}, A_{1} ,K_{2}, A_{2}, \ ... \ K_{n}, A_{n}),其中: 
          1)K_{i} ( 1 \leq i \leq n)为关键字,且关键字按升序排序; 
          2)指针A_{i}( \ 0 \leq i \leq n)指向子树的根结点,A_{i-1}指向子树中所有结点的关键字均小于K_{i},且大于K_{i-1}; 
          3)关键字的个数n必须满足: [m/2] - 1 \leq n\leq m -1
    5)所有的叶子节点都在同一层,子叶结点不包含任何信息;

B树的应用:

      文件系统的查询、插入、删除等操作;

B树的分裂:

       向B树中插入关键字,同二叉查找树中插入一个关键字类似,要查找插入新关键字的叶子结点位置。因为不能把关键字插入到一个已满的叶子结点中,故需要将一个已满的结点按其中间关键字分裂成两个结点,中间关键字被提升到该结点的父结点中。但是这种满结点的分裂动作会沿着树向上传播。为了解决这个问题,可以采取这样一种策略:当沿着树根往下查找新关键字所属位置时,就沿途分裂遇到的每个满结点。因此,每当要分裂一个满结点时,就能确保它的父结点不是满的。
 

B树的融合: 

      B树上的删除操作与插入操作类似,只是稍微复杂点,因为一个关键字能够从任意一个结点中删除,而不只是叶结点。就要我们必须保证一个结点不会因为插入而变得太大一样,必须保证一个结点不会因为删除而变得太小,删除关键字的各种情况:
     1)如果关键字k在结点x中而且x是个叶结点,则从x中删除k;
     2)如果关键字k在结点x中而且x是个内结点,则作如下操作:
            2-1)如果结点x中前于k的子结点y包含至少t个关键字,则找出k在以y为根的子树中的前驱k‘。递归的删除k’,并在x中用  k‘取代k;
            2-2)对称地,如果结点x中位于k之后的子结点z包含至少t个关键字,则找出k在以z为根的子树中的后继k’。递归的删除k‘,并在x中使用k’取代k;
             2-3)否则,如果y和z都只有t-1个关键字,则将k和z中所有关键字合并进y,使得x失去k和指向z的指针,这使y包含2t-1个关键字,然后释放z并将k从y中递归删除;
     3)如果关键字k不在内结点x中,则确定包含k的正确的子树的根ci[x]。如果ci[x]只有t-1个关键字,执行步骤3a或3b以保证我们降至一个包含至少t个关键字的结点。然后,通过对x的某个合适的子结点递归而结束;
             3-1)如果ci[x]只包含t-1个关键字,但它的一个相邻兄弟结点包含至少t个关键字,则将x中的某一个关键字降至ci[x]中,将ci[x]的相邻左兄弟或右兄弟中的某一关键字升至x,将该兄弟中合适的子结点指针移到ci[x]中,这样使得ci[x]增加一个额外的关键字;
             3-2)如果ci[x]以及ci[x]的所有相邻兄弟结点都只包含t-1个关键字,则将ci[x]与任意一个兄弟合并,则将x的一个关键字移至新合并的结点,使之成为新结点的中间关键字;

实例:建立B树,实现B树的插入、删除、打印等功能。

1、btree.h

//btree.h
#ifndef BTREE_H_
#define BTREE_H_

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define KeyType int
#define Record string

const int m = 3 ;
using namespace std;

typedef struct BTNode{
    int keynum;             //节点当前关键字个数
    KeyType key[m+1];       //关键字数组,key[0]未用
    struct BTNode *parent;  //双亲结点指针
    struct BTNode *ptr[m+1]; //孩子结点指针数组
    Record *recptr[m+1];
    BTNode(){
        keynum=0;
        parent=NULL;
        for(int i=0;i<m+1;i++)
        {
            ptr[i]=NULL;
        }
    }
}BTNode,*BTree;
BTree T = NULL ;
typedef struct{
    BTree pt;               //指向找到的结点
    int i;                  //1<=i<=m,在结点中的关键字位序
    int tag;                //1:查找成功,0:查找失败
}result;                    //B树的查找结果类型
//3. 算法设计

int Search(BTree p,int k){  //在p->key[1..p->keynum]找k
    int i=1;
    while(i<=p->keynum&&k>p->key[i]) i++;
    return i;
}
void SearchBTree(BTree t,int k,result &r){
    //在m阶B树t上查找关键字k,用r返回(pt,i,tag).
    //若查找成功,则标记tag=1,指针pt所指结点中第i个关键字等于k;
    //否则tag=0,若要插入关键字为k的记录,应位于pt结点中第i-1个和第i个关键字之间
    int i=0,found=0;
    BTree p=t,q=NULL;//初始,p指向根节点,p将用于指向待查找结点,q指向其双亲
    while(p!=NULL&&0==found){
        i=Search(p,k);
        if(i<=p->keynum&&p->key[i]==k) found = 1 ;
        else{q=p;p=p->ptr[i-1];}//指针下移
    }
    if(1==found){//查找成功,返回k的位置p及i
        r={p,i,1};
    }
    else{//查找不成功,返回k的插入位置q及i
        r={q,i,0};
    }
}
void split(BTree &q,int s,BTree &ap){//将q结点分裂成两个结点,前一半保留在原结点,后一半移入ap所指新结点
    int i,j,n=q->keynum;
    ap=(BTNode*)malloc(sizeof(BTNode));//生成新结点
    ap->ptr[0]=q->ptr[s];
    for(i=s+1,j=1;i<=n;i++,j++){    //后一半移入ap结点
        ap->key[j]=q->key[i];
        ap->ptr[j]=q->ptr[i];
    }
    ap->keynum=n-s;
    ap->parent=q->parent;
    for(i=0;i<=n-s;i++)
        if(ap->ptr[i]!=NULL) ap->ptr[i]->parent=ap;
    q->keynum=s-1;
}
void newRoot(BTree &t,BTree p,int x,BTree ap){//生成新的根结点
    t=(BTNode*)malloc(sizeof(BTNode));
    t->keynum=1;t->ptr[0]=p;t->ptr[1]=ap;t->key[1]=x;
    if(p!=NULL) p->parent = t;
    if(ap!=NULL) ap->parent=t;
        t->parent = NULL;
}
void Insert(BTree &q,int i,int x,BTree ap){//关键字x和新结点指针ap分别插到q->key[i]和q->ptr[i]
    int j,n=q->keynum;
    for(j=n;j>=i;j--){
        q->key[j+1]=q->key[j];
        q->ptr[j+1]=q->ptr[j];
    }
    q->key[i]=x;q->ptr[i]=ap;
    if(ap!=NULL) ap->parent=q;
    q->keynum++;
}
void InsertBTree(BTree &t,int k,BTree q,int i){
    //在B树中q结点的key[i-1]和key[i]之间插入关键字k
    //若插入后结点关键字个数等于b树的阶,则沿着双亲指针链进行结点分裂,使得t仍是m阶B树
    int x,s,finished = 0,needNewRoot = 0;
    BTree ap;
    if(NULL==q) newRoot(t,NULL,k,NULL);
    else{
        x=k;ap=NULL;
        while(0==needNewRoot&&0==finished){
            Insert(q,i,x,ap);//x和ap分别插到q->key[i]和q->ptr[i]
            if(q->keynum<m) finished=1;//插入完成
            else{
                s = (m+1)/2;split(q,s,ap);x=q->key[s];
                if(q->parent!=NULL){
                    q=q->parent;i=Search(q,x);//在双亲结点中查找x的插入位置
                }
                else needNewRoot=1;
            }
        }
        if(1==needNewRoot)//t是空树或者根结点已经分裂成为q和ap结点
            newRoot(t,q,x,ap);
    }
}
void Remove(BTree &p,int i)
{
    int j,n=p->keynum;
    for(j=i;j<n;j++){
        p->key[j]=p->key[j+1];
        p->ptr[j]=p->ptr[j+1];
    }
    p->keynum--;
}
void Successor(BTree &p,int i){//由后继最下层非终端结点的最小关键字代替结点中关键字key[i]
    BTree child = p->ptr[i];
    while(child->ptr[0]!=NULL) child=child->ptr[0];
    p->key[i]=child->key[1];
    p=child;
}
void Restore(BTree &p, int i,BTree &T) {//对B树进行调整
    int j;
    BTree ap=p->parent;
    if(ap==NULL) //若调整后出现空的根结点,则删除该根结点,树高减1
    {
            T=p; //根结点下移
            p=p->parent;
            return ;
    }
    BTree lc,rc,pr;
    int finished = 0 ,r=0;
    while(!finished)
    {
        r=0;
        while(ap->ptr[r]!=p) r++; //确定p在ap子树中的位置
        if(r==0)
        {
            r++;
            lc=NULL,rc=ap->ptr[r];
        }
        else if(r==ap->keynum)
        {
            rc=NULL;lc=ap->ptr[r-1];
        }
        else
        {
             lc=ap->ptr[r-1];rc=ap->ptr[r+1];
        }
        if(r>0&&lc!=NULL&&(lc->keynum>(m-1)/2))//向左兄弟借关键字
        {
            p->keynum++;
            for(j=p->keynum;j>1;j--)//结点关键字右移
            {
                 p->key[j]=p->key[j-1];
                 p->ptr[j]=p->ptr[j-1];
            }
            p->key[1]=ap->key[r];//父亲插入到结点
            p->ptr[1]=p->ptr[0];
            p->ptr[0]=lc->ptr[lc->keynum];
            if(NULL!=p->ptr[0])//修改p中的子女的父结点为p
            {
                p->ptr[0]->parent=p;
            }
            ap->key[r]=lc->key[lc->keynum];//左兄弟上移到父亲位置
            lc->keynum--;
            finished=1;
            break;
        }
        else if(ap->keynum>r&&rc!=NULL&&(rc->keynum>(m-1)/2)) //向右兄弟借关键字
        {
            p->keynum++;
            p->key[p->keynum]=ap->key[r]; //父亲插入到结点
            p->ptr[p->keynum]=rc->ptr[0];
            if(NULL!=p->ptr[p->keynum]) //修改p中的子女的父结点为p
                p->ptr[p->keynum]->parent=p;
            ap->key[r]=rc->key[1]; //右兄弟上移到父亲位置
            rc->ptr[0]=rc->ptr[1];
            for(j=1;j<rc->keynum;j++)  //右兄弟结点关键字左移
            {
                rc->key[j]=rc->key[j+1];
                rc->ptr[j]=rc->ptr[j+1];
            }
            rc->keynum--;
            finished=1;
            break;
        }
        r=0;
        while(ap->ptr[r]!=p) //重新确定p在ap子树的位置
            r++;
        if(r>0&&(ap->ptr[r-1]->keynum<=(m-1)/2)) //与左兄弟合并
        //if(r>0) //与左兄弟合并
        {
            lc=ap->ptr[r-1];
            p->keynum++;
            for(j=p->keynum;j>1;j--) //将p结点关键字和指针右移1位
            {
                p->key[j]=p->key[j-1];
                p->ptr[j]=p->ptr[j-1];
            }
            p->key[1]=ap->key[r]; //父结点的关键字与p合并
            p->ptr[1]=p->ptr[0]; //从左兄弟右移一个指针
            ap->ptr[r]=lc;
            for(j=1;j<=lc->keynum+p->keynum;j++) //将结点p中关键字和指针移到p左兄弟中
            {
                lc->key[lc->keynum+j]=p->key[j];
                lc->ptr[lc->keynum+j]=p->ptr[j];
            }
            if(p->ptr[0]) //修改p中的子女的父结点为lc
            {
                for(j=1;j<=p->keynum;j++)
                if(p->ptr[p->keynum+j])   p->ptr[p->keynum+j]->parent=lc;
            }
            lc->keynum=lc->keynum+p->keynum;  //合并后关键字的个数
            for(j=r;j<ap->keynum;j++)//将父结点中关键字和指针左移
            {
                ap->key[j]=ap->key[j+1];
                ap->ptr[j]=ap->ptr[j+1];
            }
            ap->keynum--;
            pr=p;free(pr);
            pr=NULL;
            p=lc;
        }
        else //与右兄弟合并
        {
            rc=ap->ptr[r+1];
            if(r==0)
                r++;
            p->keynum++;
            p->key[p->keynum]=ap->key[r]; //父结点的关键字与p合并
            p->ptr[p->keynum]=rc->ptr[0]; //从右兄弟左移一个指针
            rc->keynum=p->keynum+rc->keynum;//合并后关键字的个数
            ap->ptr[r-1]=rc;
            for(j=1;j<=(rc->keynum-p->keynum);j++)//将p右兄弟关键字和指针右移
            {
                rc->key[p->keynum+j]=rc->key[j];
                rc->ptr[p->keynum+j]=rc->ptr[j];
            }
            for(j=1;j<=p->keynum;j++)//将结点p中关键字和指针移到p右兄弟
            {
                rc->key[j]=p->key[j];
                rc->ptr[j]=p->ptr[j];
            }
            rc->ptr[0]=p->ptr[0]; //修改p中的子女的父结点为rc
            if(p->ptr[0])
            {
                for(j=1;j<=p->keynum;j++)
                if(p->ptr[p->keynum+j])    p->ptr[p->keynum+j]->parent=rc;
            }
            for(j=r;j<ap->keynum;j++)//将父结点中关键字和指针左移
            {
                ap->key[j]=ap->key[j+1];
                ap->ptr[j]=ap->ptr[j+1];
            }
            ap->keynum--;//父结点的关键字个数减1
            pr=p;
            free(pr);
            pr=NULL;
            p=rc;
        }
        ap=ap->parent;
        if(p->parent->keynum>=(m-1)/2||(NULL==ap&&p->parent->keynum>0))
            finished=1;
        else if(ap==NULL) //若调整后出现空的根结点,则删除该根结点,树高减1
        {
            pr=T;
            T=p; //根结点下移
            free(pr);
            pr=NULL;
            finished=1;
        }
        p=p->parent;
    }
}
void DeleteBTree(BTree &p,int i,BTree &T){//删除B树上p结点的第i个关键字
    if(p->ptr[i]!=NULL){//若不是在最下层非终端结点
        Successor(p,i);//在Ai子树中找出最下层非终端结点的最小关键字替代ki
        DeleteBTree(p,1,T);//转换为删除最下层非终端结点的最小关键字
    }else{//若是最下层非终端结点
        Remove(p,i);
        if(p->keynum<(m-1)/2)//删除后关键字个数小于(m-1)/2
            Restore(p,i,T);//调整B树
    }
}

void show_Btree(BTree &p)
{
    if(p==NULL) {puts("B tree does not exist");return ;}
    bool have_child = false ;
    printf("[");
    for(int i=1;i<=p->keynum;i++)
    {
        if(i==1) ;
        else printf(" ");
        printf("%d",p->key[i]);
    }
    printf("]");
    for(int i=0;i<=p->keynum;i++)
    {
        if(p->ptr[i]!=NULL)
        {
            if(i==0) printf("<");
            else printf(",");
            show_Btree(p->ptr[i]);
            have_child = true ;
        }
    }
    if(have_child) printf(">");
}

void show_Btree2(BTree &p,int deep)
{
    if(p==NULL) {return ;}
    int i ;
    for(i = 0 ; i < p->keynum ;i++)
    {
        show_Btree2(p->ptr[i],deep+1);
        for(int i=0;i<deep;i++)
        {
            printf("\t");
        }
        printf("%d\n",p->key[i+1]);
    }
    show_Btree2(p->ptr[i],deep+1);

}
void Destory(BTree &t)
{
    int i = 0 ;
    if(t!=NULL)
    {
        while(i<t->keynum)
        {
            Destory(t->ptr[i]);
            free(t->ptr[i]);
            i++;
        }
    }
    free(t);
    t=NULL;
}
void creat_btree()
{
    T = new BTNode ;
    T->keynum=0;
    puts("New success");
}
void insert_keytype()
{
    puts("Enter an element to be inserted");
    KeyType temp;
    scanf("%d",&temp);
    result p ;
    SearchBTree(T,temp,p);
    if(p.tag==0)
    {
        InsertBTree(T,temp,p.pt,p.i);
        puts("Insert success");show_Btree(T);
        puts("");
    }
    else puts("The element is already in the B tree.");
}
void find_keytype()
{
    puts("Enter an element to find");
    KeyType temp;
    scanf("%d",&temp);
    result p ;
    SearchBTree(T,temp,p);
    if(p.tag)
    {
        puts("Find success");
        //cout<<p.pt->recptr[p.i]<<endl;
    }
    else puts("Lookup failure");
}
void delete_keytype()
{
    puts("Enter an element to be deleted");
    KeyType temp;
    scanf("%d",&temp);
    result p ;
    SearchBTree(T,temp,p);
    if(p.tag)
    {
        DeleteBTree(p.pt,p.i,T);
        puts("Delete success");show_Btree(T);
        puts("");
    }
    else puts("The element does not exist in the B tree.");
}

#endif

2、main.cpp

//main.cpp
#include<iostream>
#include "btree.h"

using namespace std;

int main()
{
    BTree T = new BTNode ;
    T->keynum=0;
    result p ;
    int in[]={35,16,18,70,5,50,22,60,13,17,12,45,25,42,15,90,30,7};
    for(int i=0;i<18;i++)
    {
        printf("insert %d ",in[i]);
        SearchBTree(T,in[i],p);
        InsertBTree(T,in[i],p.pt,p.i);
        show_Btree(T);
        puts("");
    }
    show_Btree2(T,0);
    int out[]={45,90,50,22,42};
    for(int i=0;i<5;i++)
    {
        SearchBTree(T,out[i],p);
        if(p.tag)
        {
            printf("delete %d ",out[i]);
            DeleteBTree(p.pt,p.i,T);
            show_Btree(T);
            puts("");
             show_Btree2(T,0);
        }
        else
        {
            printf("%d is not in the tree\n",out[i]);
        }
    }
    Destory(T);
    if(T){
    	show_Btree(T);
    }
    else{
    	printf("B-tree is empty\n");
    }
    return 0;
}

结果展示:

【1】打印B树

 【2】删除叶子节点且该子孩子有多个元素(45)和删除只有单个节点的叶子结点(90)

 【3】删除中间节点50和只含单个节点的叶子结点(22)、(42)

 

practice makes perfect !

参考博客:

1、数据结构实验B树的C++代码实现

2、B树的原理与实现(C++)