http://blog.chinaunix.net/uid-20196318-id-3030529.html
B树的定义
假设B树的度为t(t>=2),则B树满足如下要求:(参考算法导论)
每个非根节点至少包含t-1个关键字,t个指向子节点的指针;至多包含2t-1个关键字,2t个指向子女的指针(叶子节点的子女为空)。
即:[t-1,2*t-1]
(2) 节点的所有key按非降序存放,假设节点的关键字分别为K[1],K[2] … K[n], 指向子女的指针分别为P[1], P[2]…P[n+1],其中n为节点关键字的个数。则有:
P[1] <=K[1] <= P[2] <= K[2] …..<= K[n] <= P[n+1] // 这里P[n]也指其指向的关键字
(3) 若根节点非空,则根节点至少包含两个子女;
(4) 所有的叶子节点都在同一层。
B树的搜索,search(root,target)
从root出发,对每个节点,找到大于或等于target关键字中最小的K[i],如果K[i]与target相等,则查找成功;否则在P[i]中递归搜索target,直到到达叶子节点,如仍未找到则说明关键字不在B树中,查找失败。
B树的插入,insert(root, target)
B树的插入需要沿着搜索的路径从root一直到叶节点,根据B树的规则,每个节点的关键字个数在[t-1,2t-1]之间,故当target要加入到某个叶子时,如果该叶子节点已经有2t-1个关键字,则再加入target就违反了B树的定义,这时就需要对该叶子节点进行分裂,将叶子以中间节点为界,分成两个包含t-1个关键字的子节点,同时把中间节点提升到该叶子的父节点中,如果这样使得父节点的关键字个数超过2t-1,则要继续向上分裂,直到根节点,根节点的分裂会使得树加高一层。
上面的过程需要回溯,那么能否从根下降到叶节点后不回溯就能完成节点的插入呢?答案是肯定的,核心思想就是未雨绸缪,在下降的过程中,一旦遇到已满的节点(关键字个数为2t-1),就就对该节点进行分裂,这样就保证在叶子节点需要分裂时,其父节点一定是非满的,从而不需要再向上回溯。
B树的删除操作:
对于一颗度为n的B树,它的关键字删除操作,需要考虑删除关键字后,节点是否还能继续保持B树的性质,如果节点拥有n-1个关键字,那么直接删除关键字的话会导致破坏B树节点关键字最小数目的限制。如果对于非叶子节点直接删除关键字,那么会影响子节点指针数组的组成,破坏了子节点指针结构。所以来说最好的办法就是把删除的关键字设法移动到叶子节点中去删除,因为叶子节点没有子节点指针数组,并且这个叶子节点的关键字数目要求至少是n,这样才能保证成功的删除关键字。
删除关键字的算法借鉴了http://blog.chinaunix.net/uid-20196318-id-3030529.html,自己重新实现了一下,自己的代码很丑。
Blog中描述的B树删除关键字的算法:(以下假设树的度是3)
1.删除的关键字在叶子节点中,直接在叶子节点中删除关键字,后面的两部会保证该叶子节点关键字的个数至少为n个。
2.如果删除的关键字在非叶子节点x中,x的数组下标为i
a.如果x的左孩子节点存在,x->child[i],并且x->child[i]的节点关键字个数至少是n个,则找到 child[i]中最大的关键字max替换x中删除的关键字,继续递归child[i]删除关键字max。
说明:左侧节点红色代表3是递归删除的新的关键字
b.如果x的右孩子节点存在,x->child[i+1],并且x->child[i+1]的节点关键字个数至少是n个,则找到 child[i+1]中最小的关键字min替换x中删除的关键字,继续递归child[i+1]删除关键字min。
c.如果x的左孩子和右孩子关键字数目都是n-1,那么这个时候应该把这个关键字和自己的左右孩子节点做合并操作(反向的分裂节点操作),继续递归删除合并节点中的关键字。
3.如果删除的关键字不再非叶子节点x中,在x节点的子节点的分支中child[i]中,且child[i]的关键字个数是n-
a.如果child[i-1]存在,并且关键字数目至少是n,那么从child[i-1]选出最大的关键字,替换x中关键字,同时这个x中的老的关键字插入到child[i]中第一个,这时child[i-1]中由于最大的关键字被移动到了父节点,这是它的子节点指针会多一个,这时从child[i-1]中删除它,正好放到child[i]子节点指针数组的第一个位置,来适应child[i]中新加入的父节点的关键字。
这个过程比较复杂,文字不好理解,如图:
说明:上图中,假设可以确定要删除的关键字在5,6分支中,那么这个分支的关键字的数量是2个(假设树的度是3),那么这个情况就不能保证case1中,如果是叶子节点直接删除关键字的说法了,所以这个时候要保证5,6分支中的关键字从别的兄弟节点中借一个。默认先从左兄弟开始借(也可以从右兄弟开始借,不成功再左兄弟)。正确的借关键字的做法就是从左(1,2,3节点)兄弟中踢掉最右侧的关键字和最右侧的子节点指针,把这个关键字(图中是3)放到父节点的4的位置上,同时把4节点放到5,6分支中,并且那个被左兄弟踢掉的子节点指针过渡到了5,6分支的第一个子节点指针的位置上。上图中红色的部分代表被移动的数据结构。
b.如果上一步失败了(左兄弟的关键字个数不够n个,或者左兄弟节点根本不存在),那么就从child[i+1]中删除第一个关键字和第一个子节点指针,关键字替代父节点的关键字,child[i]中在最后增加父节点的关键字和child[i+1]的子节点指针。类似第一步
c.如果上一步也失败了(表示从左右兄弟不能借到关键字),这时就需要child[i]和child[i-1]或者是child[i+1](child[i-1],child[i+1]至少有个子节点存在)合并到一起(如果左右兄弟节点都存在的话,合并的顺序可以随意,但是如果其中一个兄弟节点为空,为空是因为关键字是节点最后一个,或者是第一个关键字,这样就只能合并那个存在的兄弟节点),这个过程就是B树插入关键字的逆操作,然后在递归删除这个合并节点中的关键字。
说明:上图中的情况,查找关键字在4,5分支中,但是左右兄弟都是n-1个关键字,这是兄弟把4,5兄弟节点1,2(左兄弟合并到4,5节点上)。
下面是代码:
#include <iostream>
#define M 6
//b树的数据结构
typedef structbtree_node
{
int k[2*M-1];//每个节点最多包含M-1个节点最少包涵M-1个节点
struct btree_node *p[2*M];//每层最多包含*M个指针,最少M个指针
int num;
bool is_leaf;
}btree_node;
//创建B树
void btree_delete_nonone(btree_node* root,int target);
btree_node* btree_node_new()
{
btree_node*node=(btree_node*)malloc(sizeof(btree_node));
if(NULL==node)
return NULL;
for(inti=0;i<2*M-1;i++)
node->k[i]=0;
for(inti=0;i<2*M;i++)
node->p[i]=NULL;
node->num=0;
node->is_leaf=true;//默认是叶子节点
return node;
}
btree_node* btree_create()
{
btree_node*node=btree_node_new();
if(NULL==node)
return NULL;
return node;
}
//插入节点
//当child满的时候,对其进行分裂child=parent->p[pos]
int btree_split_child(btree_node* parent,int pos,btree_node* child)
{
//创建新的节点
btree_node*newchild=btree_node_new();
if(NULL==newchild)
return -1;
newchild->is_leaf=child->is_leaf;
newchild->num=M-1;
//将child的后半部分拷贝给新节点
for(inti=0;i<M-1;i++)
newchild->k[i]=child->k[i+M];
//如果child不是叶子及诶单,还需要把指针拷贝过去,指针比节点多
if(false==newchild->is_leaf)
{
for(inti=0;i<M;i++)
newchild->p[i]=child->p[i+M];
}
child->num=M-1;
//child的中间节点需要插入parent的pos处,更新parent的key和pointer
for(inti=parent->num;i>pos;i--)
parent->p[i+1]=parent->p[i];
parent->p[pos+1]=newchild;
for(inti=parent->num-1;i>=pos;i--)
parent->k[i+1]=parent->k[i];
parent->k[pos]=child->k[M-1];
parent->num+=1;
}
void btree_insert_nonfull(btree_node* node,int target)
{
if(1==node->is_leaf)
{
int pos=node->num;
while(pos>=1&&target<node->k[pos-1])
{
node->k[pos]=node->k[pos-1];
pos--;
}
node->k[pos]=target;
node->num+=1;
}
else
{//查找
int pos=node->num;
while(pos>0&&target<node->k[pos-1])
pos--;
if(2*M-1==node->p[pos]->num)
{//若p的某个子节点满了
btree_split_child(node,pos,node->p[pos]);
if(target>node->k[pos])
pos++;
}
btree_insert_nonfull(node->p[pos],target);
}
}
btree_node* btree_insert(btree_node*root,int target)
{
if(NULL==root)
return NULL;
if(2*M-1==root->num)
{
btree_node*node=btree_node_new();
if(NULL==node)
return NULL;
node->is_leaf=0;
node->p[0]=root;
btree_split_child(node,0,root);//分裂根节点增高树根
btree_insert_nonfull(node,target);
return node;
}
else
{
btree_insert_nonfull(root,target);
return root;
}
}
void levelOrderTraverse(btree_node* root)
{
btree_node*q[100];
int tail=1;
int head=0;
q[head]=root;
while(head<tail)
{
btree_node*temp=q[head++];
printf("[");
for(inti=0;i<temp->num;i++)
printf("%d ",temp->k[i]);
printf("] ");
for(inti=0;i<=temp->num;i++)
if(NULL!=temp->p[i])
q[tail++]=temp->p[i];
}
printf("\n");
}
void inOrderTraverse(btree_node* root)
{
if(root!=NULL)
{
inOrderTraverse(root->p[0]);
for(inti=0;i<root->num;i++)
{
printf("%d ",root->k[i]);
inOrderTraverse(root->p[i+1]);
}
}
}
void preOrderTraverse(btree_node* root)
{
if(NULL!=root)
{
for(inti=0;i<root->num;i++)
{
printf("%d ",root->k[i]);
}
for(inti=0;i<=root->num;i++)
{
preOrderTraverse(root->p[i]);
}
}
}
void postOrderTraverse(btree_node* root)
{
if(NULL!=root)
{
for(inti=0;i<=root->num;i++)
postOrderTraverse(root->p[i]);
for(inti=0;i<root->num;i++)
printf("%d ",root->k[i]);
}
}
//寻找rightmost,以root为根的最大关键字
int btree_search_predecessor(btree_node* root)
{
btree_node*y=root;
while(false==y->is_leaf)
{
y=y->p[y->num];
}
return y->k[y->num-1];
}
//寻找以root为根的最小关键字
int btree_search_successor(btree_node* root)
{
btree_node*z=root;
while(false==z->is_leaf)
z=z->p[0];
return z->k[0];
}
//
void btree_shift_to_right_child(btree_node* root,int pos,btree_node* y,btree_node* z)
{
z->num+=1;
for(inti=z->num-1;i>0;i--)
z->k[i]=z->k[i-1];
z->k[0]=root->k[pos];
root->k[pos]=y->k[y->num-1];
if(false==z->is_leaf)
{
for(inti=z->num;i>0;i--)
z->p[i]=z->p[i-1];
z->p[0]=y->p[y->num];
}
y->num-=1;
}
void btree_shift_to_left_child(btree_node* root,int pos,btree_node* y,btree_node* z)
{
y->num+=1;
y->k[y->num-1]=root->k[pos];
root->k[pos]=z->k[0];
for(intj=1;j<z->num;j++)
z->k[j-1]=z->k[j];
if(false==z->is_leaf)
{
y->p[y->num]=z->p[0];
for(intj=1;j<=z->num;j++)
z->p[j-1]=z->p[j];
}
z->num-=1;
}
//将y,root->k[pos],z合并到y节点,释放z节点,y和z各有M-1个节点
void btree_merge_child(btree_node* root,int pos,btree_node* y,btree_node* z)
{
y->num=2*M-1;
for(inti=M;i<2*M-1;i++)
y->k[i]=z->k[i-M];
y->k[M-1]=root->k[pos];
if(false==z->is_leaf)
for(inti=M;i<2*M;i++)
y->p[i]=z->p[i-M];
for(intj=pos+1;j<root->num;j++)
{
root->k[j-1]=root->k[j];
root->p[j]=root->p[j+1];
}
root->num-=1;
free(z);
}
btree_node* btree_delete(btree_node*root,int target)
{
//当根只有两个子女时候,且两个子女关键字个数都等于M-1的时候合并根和两个子女
//降低了树高
if(1==root->num)
{
btree_node*y=root->p[0];
btree_node*z=root->p[1];
if(NULL!=y&&NULL!=z&&M-1==y->num&&M-1==z->num)
{
btree_merge_child(root,0,y,z);//合并y和z和root
free(root);
btree_delete_nonone(y,target);
return y;
}
else
{
btree_delete_nonone(root,target);
return root;
}
}
}
void btree_delete_nonone(btree_node* root,int target)
{
if(true==root->is_leaf)
{//如果在叶子节点则直接删除
int i=0;
while(i<root->num&&target>root->k[i])
i++;
if(target==root->k[i])
{
for(intj=i+1;j<2*M-1;j++)
root->k[j-1]=root->k[j];
root->num-=1;
}
else
{
printf("target not found\n");
}
}
else
{//如果删除的节点不在叶子
int i=0;
btree_node*y=NULL,*z=NULL;
while(i<root->num&&target>root->k[i])
i++;
if(i<root->num&&target==root->k[i])
{//如果在分支中找到了target
y=root->p[i];
z=root->p[i+1];
if(y->num>M-1)
{//如果左分支关键字多于M-1,则找到其做分支中的最右节点prev,替换target
//并在左分支中递归删除prev
int pre=btree_search_predecessor(y);
root->k[i]=pre;
btree_delete_nonone(y,pre);
}
else if(z->num>M-1)
{//如果右分支关键字多余M-1
int next=btree_search_successor(z);
root->k[i]=next;
btree_delete_nonone(z,next);
}
else
{
//两个分支都正好为M-1都不能删除
//将两个分支与y和z与root[i]合并
btree_merge_child(root,i,y,z);
btree_delete(y,target);
}
}
else
{//在分支中没有找到,肯定在分支的子节点中
y=root->p[i];
if(i<root->num)
z=root->p[i+1];//后一个
btree_node*p=NULL;
if(i>0)
p=root->p[i-1];//前一个子树
if(y->num==M-1)
{
if(i>0&&p->num>M-1)
{
//左临界点关键字个数大于M-1
btree_shift_to_right_child(root,i-1,p,y);
}
elseif(i<root->num&&z->num>M-1)
{
//有临界点关键字个数大于M-1
btree_shift_to_left_child(root,i,y,z);
}
elseif(i>0)
{
//需要合并
btree_merge_child(root,i-1,p,y);
y=p;
}
else
{//
btree_merge_child(root,i,y,z);
}
btree_delete_nonone(y,target);
}
else
{
btree_delete_nonone(y,target);
}
}
}
}
#include <time.h>
int main()
{
int arr[] = {18, 31, 12, 10, 15, 48, 45, 47, 50, 52, 23, 30,20};
btree_node *root = btree_create();
for(inti = 0; i <sizeof(arr) / sizeof(int); i++) {
root = btree_insert(root, arr[i]);
}
levelOrderTraverse(root);
inOrderTraverse(root);
printf("\n");
preOrderTraverse(root);
printf("\n");
postOrderTraverse(root);
printf("\n");
int todel[] = {52};
for(int i = 0; i <sizeof(todel) /sizeof(int); i++) {
printf("after delete %d\n", todel[i]);
root= btree_delete(root, todel[i]);
levelOrderTraverse(root);
inOrderTraverse(root);
printf("\n");
preOrderTraverse(root);
printf("\n");
postOrderTraverse(root);
printf("\n");
}
return 0;
}
截图: M=6