虽然前面把伪代码进行了阐述,但是想要真正的编写出来还是非常的不容易,鉴于能力有限,本人的实现版本如下(和伪代码差距有点大,码力差距还是和别人有很大)
#include <stdio.h>
#include <stdlib.h>
#define m 4
//B_树的数据结构
typedef struct node{
int Numbers;
int val[m+1];
char Key[m+1];
struct node *ptr[m+1];
struct node *Parent;
}BNode,*PBNode;
//传递查找信息
typedef struct re{
bool tag; //1表示查找到,0表示未查找到
int Index; //查找到的下标
PBNode Result; //记录最后查找的节点
}Record;
//插入Key和val,P是要插入的节点,Root是树的根节点,返回0表示是刷新原来的值
int InsertKey(PBNode *Root,PBNode P,char Key,int val);
//查找Key并且返回含有Key的节点的相关信息
bool SearchKey(PBNode Root,Record *Result,char Key);
//在指定节点查找,但是并不一定会查找到,有可能查找的是插入的下标
int Search(PBNode Node,char Key);
//执行在指定节点插入的函数
void InsertInNode(PBNode p,int InsertIndex,char Key,int val);
//分裂函数,当插入时候子节点的个数大于M的时候就要进行分裂,将前一半的数据留在原来的节点,后一半的数据放入新节点
PBNode Split(PBNode Node);
//利用插入函数的多次调用直接创建一棵B-树
void CreateTree(PBNode *Root,char *Key,int *Data,int KeySize);
//采用类似先序遍历的方法来进行B-树的遍历
void Display(PBNode Root);
//从左兄弟借键
void LeftSilb(PBNode Left,PBNode right,PBNode Parent,int DeleteIndex);
//从右兄弟借键
void RightSilb(PBNode Left,PBNode right,PBNode Parent,int DeleteIndex);
//当左右节点都没有键可以借用的时候,从父节点借用并且合并,和左节点合并
void MergeWithLeft(PBNode Left,PBNode Parent,PBNode Right,int DeleteIndex,bool flag);
//当左右节点都没有键可以借用的时候,从父节点借用并且合并,和右节点合并
void MergeWithRight(PBNode Left,PBNode Parent,PBNode Right,int DeleteIndex,bool flag);
//删除指定的键值
bool DeleteKey(PBNode *Root,char Key);
//判断节点是否是叶子节点true代表是叶子节点
bool IsLeave(PBNode Node);
//在删除的时候需要迭代的情况
void IterativeDelete(PBNode *Root,PBNode p,int Border,int Index);
//Modify Parent Point
void ModifyParent(PBNode Root);
//如果删除的是非叶子节点则需要转换,选择左节点的最大值,Index是要交换的Key位置
PBNode LeftMaxToNode(PBNode p,int Index);
//如果删除的是非叶子节点则需要转换,选择右节点的最小值,Index是要交换的Key的位置
PBNode RightMinToNode(PBNode p,int Index);
由于代码的冗余,可能代码量有点多,所以我只贴出最关键的函数实现:
//Modify Parent Point
void ModifyParent(PBNode Root)
{
int i;
if(!Root)
return;
for(i = 0;i < Root->Numbers;i++)
{
if(Root->ptr[i])
Root->ptr[i]->Parent = Root;
ModifyParent(Root->ptr[i]);
}
//最右边的分支
ModifyParent(Root->ptr[i]);
if(Root->ptr[i])
Root->ptr[i]->Parent = Root;
}
//在指定节点查找,但是并不一定会查找到,有可能查找的是插入的下标
int Search(PBNode Node,char Key)
{
if(!Node)
return -1;
int i,j = -1;
for(i = 0;i < Node->Numbers;i++)
{
if(Node->Key[i] < Key)
j = i;
}
return j;
}
//查找Key并且返回含有Key的节点的相关信息
bool SearchKey(PBNode Root,Record *Result,char Key)
{
int Index = Search(Root,Key);
bool flag = false;
PBNode Temp = Root;
PBNode Parent = Root;
int LastIndex = Index+1;
while(!flag)
{
//表示没查找到,但是返回应该要插入的叶子节点的信息
if(!Temp)
{
(*Result).Index = LastIndex;
(*Result).Result = Parent;
(*Result).tag = 0;
return false;
}
if(Temp->Key[Index+1] == Key)
{
(*Result).Index = Index+1;
(*Result).Result = Temp;
(*Result).tag = 1; //表示查找到
flag = true;
return flag;
}//if
Parent = Temp;
Temp = Temp->ptr[Index+1];
LastIndex = Index+1;
Index = Search(Temp,Key); //因为Root->Key[Index]是小于Key的并且Root->Key[Index+1]是大于Key的
}//while
return false;
}
//分裂函数,当插入时候子节点的个数大于M的时候就要进行分裂,将前一半的数据留在原来的节点,后一半的数据放入新节点
PBNode Split(PBNode Node)
{
//m/2始终是分裂提上父节点的Key
int mid = m/2,i,j;
PBNode RightNode = (PBNode)malloc(sizeof(BNode));
for(i = mid+1;i < Node->Numbers;i++)
{
RightNode->Key[i-mid-1] = Node->Key[i];
RightNode->val[i-mid-1] = Node->val[i];
RightNode->ptr[i-mid-1] = Node->ptr[i];
if(RightNode->ptr[i-mid-1])
RightNode->ptr[i-mid-1]->Parent = RightNode;
}
//最后一个分支
RightNode->ptr[i-mid-1] = Node->ptr[i];
if(RightNode->ptr[i-mid-1])
RightNode->ptr[i-mid-1]->Parent = RightNode;
for(j = i-mid;j < m+1;j++)
RightNode->ptr[j] = NULL;
//将原来节点的右侧分支全部断开
for(j = m/2+1;j < Node->Numbers+1;j++)
Node->ptr[j] = NULL;
//便于调试
for(i = mid+1;i < Node->Numbers;i++)
Node->Key[i] = ' ';
RightNode->Numbers = Node->Numbers-mid-1;
Node->Numbers = mid;
return RightNode;
}
//插入Key和val,P是要插入的节点,Root是树的根节点,返回0表示是刷新原来的值
int InsertKey(PBNode *Root,char Key,int val)
{
Record Result;
PBNode p;
int Index,k;
if(SearchKey((*Root),&Result,Key))
{
Result.Result->val[Result.Index] = val;
return 0; //表示只是刷新值
}
p = Result.Result;
Index = Result.Index;
bool finish = false;
if(p)
{
InsertInNode(p,Index,Key,val);
if(p->Numbers < m)
return true;
}
//此处的循环是为了出现多次分裂的情况!
while(!finish && p)
{
//出现分裂的情况
//else
//{
PBNode Right = Split(p);
if(p->Parent)
{
//寻找要插入到父节点的哪个位置
Index = Search(p->Parent,p->Key[m/2])+1;
//将Key插入父亲节点
InsertInNode(p->Parent,Index,p->Key[m/2],p->val[m/2]);
if(p->Parent->Numbers < m)
finish = true;
//便于调试
//p->Key[m/2] = ' ';
p->Parent->ptr[Index] = p;
p->Parent->ptr[Index+1] = Right;
Right->Parent = p->Parent;
Key = p->Key[m/2];
val = p->val[m/2];
//便于调试
p->Key[m/2] = ' ';
p = p->Parent;
}
//表示需要分裂父节点
else
{
PBNode NewParent = (PBNode)malloc(sizeof(BNode));
NewParent->Numbers = 1;
NewParent->Key[0] = p->Key[m/2];
NewParent->val[0] = p->val[m/2];
NewParent->Parent = NULL;
NewParent->ptr[0] = p;
NewParent->ptr[1] = Right;
p->Parent = Right->Parent = NewParent;
//便于调试
p->Key[m/2] = ' ';
(*Root) = NewParent;
for(k = 2;k < m+1;k++)
NewParent->ptr[k] = NULL;
finish = true;
}
//}
}//while
//表示空节点
if(!p)
{
(*Root) = (PBNode)malloc(sizeof(BNode));
(*Root)->Key[0] = Key;
(*Root)->val[0] = val;
for(k = 0;k < m+1;k++)
(*Root)->ptr[k] = NULL;
(*Root)->Numbers = 1;
(*Root)->Parent = NULL;
}
return 1; //一定会成功
}
//当左右节点都没有键可以借用的时候,从父节点借用并且合并,和左节点合并,右子树有Key需要删除
void MergeWithLeft(PBNode Left,PBNode Parent,PBNode Right,int DeleteIndex,bool flag)
{
int i,Way;
//判断分支
for(i = 0;i < Parent->Numbers;i++)
{
if(Left == Parent->ptr[i])
break;
}
Way = i;
//右子树的装填,删除并且从父亲那里借键
for(i = DeleteIndex-1;i >= 0;i--)
{
Right->Key[i+1] = Right->Key[i];
Right->ptr[i+1] = Right->ptr[i];
Right->val[i+1] = Right->val[i];
}
Right->Key[0] = Parent->Key[Way];
Right->val[0] = Parent->val[Way];
//Right->ptr[0] = NULL;
if(flag)
Right->Numbers++;
//合并,直接采用原来左边的节点
for(i = 0;i < Right->Numbers;i++)
{
Left->Key[Left->Numbers+i] = Right->Key[i];
Left->val[Left->Numbers+i] = Right->val[i];
if(i != 0)
{
Left->ptr[Left->Numbers+i] = Right->ptr[i];
if(Right->ptr[i])
Right->ptr[i]->Parent = Left->ptr[Left->Numbers+i];
}
}
Left->ptr[Left->Numbers+i] = Right->ptr[i-1];
if(Right->ptr[i-1])
Right->ptr[i-1] = Left->ptr[Left->Numbers+i];
Left->Numbers = Left->Numbers+Right->Numbers;
//父节点后面的节点向前进行一个搬迁
//for(i = Way+1;i < Parent->Numbers-1;i++)
//{
// Parent->Key[i] = Parent->Key[i+1];
// Parent->val[i] = Parent->val[i+1];
// Parent->ptr[i] = Parent->ptr[i+1];
//}
//Parent->ptr[Parent->Numbers-1] = Parent->ptr[Parent->Numbers];
Parent->Numbers--;
}
//如果删除的是非叶子节点则需要转换,选择左节点的最大值
PBNode LeftMaxToNode(PBNode p,int Index)
{
PBNode Temp;
int i,Remember = Index;
for(i = 0;i <= Index;i++)
{
if(p->ptr[i])
break;
}
//左边节点全为空
if(i == Index+1)
return NULL;
Temp = p->ptr[i];
//左节点的最大值(右边)
while(!IsLeave(Temp))
{
for(i = Temp->Numbers;i >= 0;i--)
{
if(Temp->ptr[i])
break;
}
if(i == -1)
break;
Temp = Temp->ptr[i];
}
char T = p->Key[Remember];
int TT = p->val[Remember];
p->Key[Remember] = Temp->Key[Temp->Numbers-1];
p->val[Remember] = Temp->val[Temp->Numbers-1];
Temp->Key[Temp->Numbers-1] = T;
Temp->val[Temp->Numbers-1] = TT;
return Temp;
}
//删除指定的键值
bool DeleteKey(PBNode *Root,char Key)
{
Record Re;
int i,way;
//并没有查找到,删除失败
if(!SearchKey((*Root),&Re,Key))
return false;
bool finish = false;
//Border是[m/2]-1向上取整
int Border = (m%2==0) ? (m/2-1):(m/2);
PBNode p = Re.Result;
//for(i = 0;i < p->Parent->Numbers;i++)
//{
// if(p->Parent->ptr[i] == p)
// break;
//}
//要删除的节点是父节点的第几个分支
//Way = i;
//用于迭代的进入条件
if(!(p->Parent) && IsLeave(p))
{
for(i = Re.Index;i < p->Numbers;i++)
{
p->Key[i] = p->Key[i+1];
p->val[i] = p->val[i+1];
}
p->Numbers--;
return true;
}
bool flag = false;
while(!finish)
{
if(!(p->Parent) && IsLeave(p))
{
for(i = Re.Index;i < p->Numbers;i++)
{
p->Key[i] = p->Key[i+1];
p->val[i] = p->val[i+1];
}
p->Numbers--;
return true;
}
if(p->Parent)
{
for(i = 0;i < p->Parent->Numbers+1;i++)
{
if(p->Parent->ptr[i] == p)
break;
}
//要删除的节点是父节点的第几个分支
way = i;
}
else
way = 0;
//叶子节点并且节点个数大于Border
if(IsLeave(p) && p->Numbers > Border)
{
for(i = Re.Index;i < p->Numbers;i++)
{
p->Key[i] = p->Key[i+1];
p->val[i] = p->val[i+1];
}
p->Numbers--;
finish = true;
}
//叶子节点且是第一个分支
else if((IsLeave(p) || flag) && way == 0)
{
//右兄弟可以借键
if(p->Parent->ptr[1]->Numbers > Border)
{
RightSilb(p,p->Parent->ptr[1],p->Parent,Re.Index);
finish = true;
}
//右兄弟没有多余的键
else
{
MergeWithRight(p,p->Parent,p->Parent->ptr[1],Re.Index,flag);
//是否需要继续递归
if(p->Parent->Numbers >= Border || (!(p->Parent->Parent)&&p->Parent->Numbers > 0))
{
//因为结束了,所以要modify父节点
for(i = way;i < p->Parent->Numbers;i++)
{
p->Parent->Key[i] = p->Parent->Key[i+1];
p->Parent->val[i] = p->Parent->val[i+1];
}
for(i = 1;i < p->Parent->Numbers+1;i++)
p->Parent->ptr[i] = p->Parent->ptr[i+1];
//便于调试
p->Parent->Key[p->Parent->Numbers] = ' ';
p->Parent->val[p->Parent->Numbers] = -1;
p->Parent->ptr[p->Parent->Numbers+1] = NULL;
finish = true;
}
//表示根节点被分解,那么现在的节点成为根节点
else if(p->Parent->Numbers == 0 && !(p->Parent->Parent))
{
(*Root) = p;
(*Root)->Parent = NULL;
finish = true;
}
else
{
p = p->Parent;
Re.Index = 0;
flag = true;
}
}
}
//叶子节点且是最后一个分支
else if((IsLeave(p) || flag) && way == p->Parent->Numbers)
{
//左兄弟可以借键
if(p->Parent->ptr[p->Parent->Numbers-1]->Numbers > Border)
{
LeftSilb(p->Parent->ptr[p->Parent->Numbers-1],p,p->Parent,Re.Index);
finish = true;
}
//左兄弟没有多余的键
else
{
MergeWithLeft(p->Parent->ptr[p->Parent->Numbers-1],p->Parent,p,Re.Index,flag);
//是否需要继续递归
if(p->Parent->Numbers >= Border || (!(p->Parent->Parent)&&p->Parent->Numbers > 0))
{
//因为结束了,所以要modify父节点
for(i = way-1;i < p->Parent->Numbers;i++)
{
p->Parent->Key[i] = p->Parent->Key[i+1];
p->Parent->val[i] = p->Parent->val[i+1];
}
//for(i = way;i < p->Parent->Numbers+1;i++)
// p->Parent->ptr[i] = p->Parent->ptr[i+1];
//便于调试
p->Parent->Key[p->Parent->Numbers] = ' ';
p->Parent->val[p->Parent->Numbers] = -1;
p->Parent->ptr[p->Parent->Numbers+1] = NULL;
finish = true;
}
//表示根节点被分解,那么现在的节点成为根节点
else if(p->Parent->Numbers == 0 && !(p->Parent->Parent))
{
(*Root) = p->Parent->ptr[way-1];
(*Root)->Parent = NULL;
finish = true;
}
else
{
p = p->Parent;
Re.Index = p->Numbers-1;
flag = true;
}
}
}
//叶子节点,但是是中间节点
else if((IsLeave(p) || flag) && 0 < way && way < p->Parent->Numbers)
{
//左兄弟有多余的键
if(p->Parent->ptr[way-1]->Numbers > Border)
{
LeftSilb(p->Parent->ptr[way-1],p,p->Parent,Re.Index);
finish = true;
}
//右兄弟有多余的键
else if(p->Parent->ptr[way+1]->Numbers > Border)
{
RightSilb(p,p->Parent->ptr[way+1],p->Parent,Re.Index);
finish = true;
}
//左右兄弟都没有多余的键,选取左子树进行合并
else
{
MergeWithLeft(p->Parent->ptr[way-1],p->Parent,p,Re.Index,flag);
//是否需要继续递归
if(p->Parent->Numbers >= Border || (!(p->Parent->Parent)&&p->Parent->Numbers > 0))
{
//因为结束了,所以要modify父节点
for(i = way-1;i < p->Parent->Numbers;i++)
{
p->Parent->Key[i] = p->Parent->Key[i+1];
p->Parent->val[i] = p->Parent->val[i+1];
}
//便于调试
p->Parent->Key[p->Parent->Numbers] = ' ';
p->Parent->val[p->Parent->Numbers] = -1;
for(i = Re.Index+1;i < p->Parent->Numbers+1;i++)
p->Parent->ptr[i] = p->Parent->ptr[i+1];
p->Parent->ptr[p->Parent->Numbers+1] = NULL;
finish = true;
}
//表示根节点被分解,那么现在的节点成为根节点
else if(p->Parent->Numbers == 0 && !(p->Parent->Parent))
{
(*Root) = p->Parent->ptr[way-1];
(*Root)->Parent = NULL;
finish = true;
}
else
{
p = p->Parent;
Re.Index = 0;
flag = true;
}
}
}
//非叶子节点
else
{
p = LeftMaxToNode(p,Re.Index);
if(p)
Re.Index = p->Numbers-1;
else
{
p = RightMinToNode(p,Re.Index);
Re.Index = 0;
}
}
}//while
ModifyParent(*Root);
}
以上!
更加简便的见这位仁兄的:https://blog.csdn.net/geek_jerome/article/details/78895289