B-Tree的C++实现

简要说明下B树的性质。

用M表示B树的阶数,L表示叶子节点的最大元素个 

(性质说明来自于《数据结构与问题求解(C++版)》第19章) 

1、数据项保存在叶子中

 2、非叶子节点具有M-1个键指导查找的进行;键i代表子树i+1中最小的键 

3、根要么是叶子,要么就有2~M个孩子 

4、所有非叶子节点,(根除外)都具有[M/2]~M个孩子

 5、所有叶子都在同一深度,并且对某一叶子,具有[L/2]~L个数据项

 在对B树进行插入操作时,如果某个叶子中的元素个数已经达到L个,那么这时就需要对叶子进行分裂。而叶子的分裂同时也可能引起父节点的分裂。造成分裂的效果可能会依次往上。直到造成根的分裂,产生一个新的根才终止,当然这种情况是很少的。在对B树进行删除时,可能会使得叶子中元素的个数不满足规则5。这个时候就需要进行叶子的合并了。而叶子的合并也可能会造成父节点的合并,这种影响同样可能会向上传递,直到根的孩子个数减为0,从而造成树的高度减一才终止。这种情形依旧是很少见的

#include<iostream>
using std::cout;
using std::endl;
using std::ostream;
//定义B树的阶数
#define M 4
#define L 3
class BTree
{
private:
int child_size;   //若此节点为非叶子节点,表示此节点的子节点个数
int elem_size;    //若此节点为叶子节点,表示此叶子所含的元素个数
bool is_leaf; 
BTree* parent;
BTree* root;
/*
如果此节点是叶子节点,那么就使用elem_data部分
如果此节点是非叶子节点,那么就需要child_list来保存指向其子节点的指针, 并且还需要有index_key(对应第二条性质)
*/
union{       
struct{
BTree* child_list[M];
int index_key[M-1];
};
int elem_data[L];
};
public:
BTree();
void insert(int);   //插入一个元素
void insert(int*,int);  //插入一个数组
~BTree();
void remove(int);
/*查找某个值是否在记录中,找到返回1,否则返回0*/
//int find(int);
BTree* getRoot();
/*
按照层次将数输出
*/
friend ostream& operator<<(ostream& os,BTree);
/*检查树的各项指针的指向是否正确*/
friend bool check_tree(BTree* root);
private:
/*
当往树中插入数据引起树中节点的分裂时使用。
*/
void node_break( BTree&, BTree&); 
/*
当从树中删除元素引起叶子合并时使用
*/
void node_merge(BTree&);
/*
往一个有序数组中插入一个元素,插入完之后要保证数组中的元素依旧是有序的
array 为要插入数据的数组
data 为待插入的数据
size 为数组中已有的元素个数
*/
void array_insert(int* array,int data, int size);
/*
从一个有序数组中删除一个元素,删除完之后要保证数组中的元素依旧是有序的
*/
int array_remove(int* array,int data, int size);
/*
取得某一颗子树的最小值
*/
int get_min_value(const BTree&);
/*
返回一个节点的元素个数或者子树个数
*/
int get_size(const BTree&);
};


/*
B树,简要说明下B树的性质。M表示B树的阶数,L表示叶子节点的最大元素个


(性质说明来自于《数据结构与问题求解(C++版)》第19章)
1、数据项保存在叶子中
2、非叶子节点具有M-1个键指导查找的进行;键i代表子树i+1中最小的键
3、根要么是叶子,要么就有2~M个孩子
4、所有非叶子节点,(根除外)都具有[M/2]~M个孩子
5、所有叶子都在同一深度,并且对某一叶子,具有[L/2]~L个数据项
*/
#include"btree.h"
#include<vector>
#include<ctime>
using std::ctime;
using std::vector;


/*
接口功能函数的实现
*/
BTree::BTree()
{
is_leaf=true;
elem_size=0;
parent=NULL;
root=NULL;
child_size=0;
}
BTree::~BTree()
{
if(!is_leaf)
{
for(int i=0;i<child_size;i++)
delete child_list[i];
delete root;
}
}
BTree* BTree::getRoot(){ return root;}
void BTree::insert(int* array,int num)
{
for(int i=0;i<num;i++)
insert(*(array+i));
}
void BTree::insert(int data)
{
/*
如果树为空,或者树的根节点为叶子节点,就直接把这个元素插入到元素列表中
这个时候,要考虑到可能会引起分裂
*/
if(!root)   //root为空
{
root=new BTree;
root->is_leaf=true;
root->parent=NULL;
root->elem_data[root->elem_size]=data;
root->elem_size=1;
}
else      
{
/*首先找到要插入的叶子节点*/
BTree* p=root;
while(!(p->is_leaf))
{
int i=0;
for(;i<p->child_size-1;i++)
{
if(data<p->index_key[i])
break;
}
p=p->child_list[i];
}
/*检查此叶子节点是否已满*/
if(p->is_leaf)
{
/*如果元素已满,就需要将叶子分裂。否则就直接插入元素即可*/
if(p->elem_size==L)
{
/*用一个临时数组将L+1个数都有序保存起来*/
int tempArray[L+1];
for(int i=0;i<L;i++)
tempArray[i]=p->elem_data[i];
array_insert(tempArray,data,L);
/*
cout<<"[test] ";
for(int i=0;i<=L;i++)
cout<<tempArray[i]<<" ";
cout<<"[test ends]"<<" ";
*/
int num=(L+1)/2;
p->elem_size=num;
for(int i=0;i<num;i++){
p->elem_data[i]=tempArray[i];
//cout<<tempArray[i]<<" ";
}
BTree* newLeaf=new BTree;
newLeaf->is_leaf=true;
newLeaf->elem_size=L+1-num;
for(int i=0;i<newLeaf->elem_size;i++){
newLeaf->elem_data[i]=tempArray[num+i];
//cout<<tempArray[num+i]<<" ";
}
//cout<<endl;
/*
在调用node_break进行分裂的时候
并没有改变第一个参数代表的节点与其父节点的关系
*/
node_break(*p,*newLeaf);
}
else
{
array_insert(p->elem_data,data,p->elem_size);
p->elem_size=p->elem_size+1;
}
}
else  //find p, but not a leaf
{
cout<<"something is wrong..."<<endl;
}
}
}
/*
* 辅助功能函数的实现
* 使用插入排序算法思想来实现插入数据
* 这里不考虑数组的溢出问题,这个由调用者保证
*/
void BTree::array_insert(int* array,int data,int size)
{
int index=0;
for(;index<size;index++)
if(data<*(array+index))
break;
for(int i=size;i>index;i--)
array[i]=array[i-1];
array[index]=data;


}
/*
第一个参数表示旧节点被分裂之后的第一个节点(含有旧节点的父节点信息)
第二个参数表示分裂之后新增加的节点(无旧节点的父节点信息)
*/
void BTree::node_break(BTree& lNode, BTree& rNode)
{
if(lNode.parent)  //如果原节点的父节点不为空
{
BTree* parent=lNode.parent;
/*判断子节点的分裂是否会引起父节点的分裂*/
if((parent->child_size)==M)   //子节点的分裂引起了父节点的分裂
{
int insert_index=0; //找出插入的点
for(;insert_index<M;insert_index++)
if(parent->child_list[insert_index]==&lNode)
break;


//将所有要分配的子树用一个统一的数组保存起来
BTree* temp_child_list[M+1];
for(int i=0;i<=insert_index;i++)
temp_child_list[i]=parent->child_list[i];
temp_child_list[insert_index+1]=&rNode;
for(int i=insert_index+2;i<M+1;i++)
temp_child_list[i]=parent->child_list[i-1];
/*
cout<<"[test] "
for(int i=0;i<M+1;i++)
cout<<get_min_value(*(temp_child_list[i]))<<" ";
cout<<"[test...end]"<<endl;
/*
现在开始进行子树分裂,将重新分配这些指向子树的指针
同时要改变这些子树的父节点指针
*/
int num=(M+1)/2;
for(int i=0;i<num;i++)
{
parent->child_list[i]=temp_child_list[i];
parent->child_list[i]->parent=parent;
}
parent->child_size=num;
//创建新的父节点
BTree* new_parent=new BTree;
new_parent->is_leaf=false;
for(int i=0;i<M+1-num;i++)
{
new_parent->child_list[i]=temp_child_list[num+i];
new_parent->child_list[i]->parent=new_parent;
}
new_parent->child_size=M+1-num;
/*
到此,全部的子树已经分割完毕
剩下的就是为两个父节点设置索引值
*/
for(int i=0;i<parent->child_size-1;i++)
parent->index_key[i]=    \
get_min_value(*(parent->child_list[i+1]));
for(int i=0;i<new_parent->child_size-1;i++)
new_parent->index_key[i]= \
get_min_value(*(new_parent->child_list[i+1]));
/*
然后继续调整,解决父节点的分裂对更高层节点造成的影响
*/
node_break(*parent,*new_parent);


}
else //子节点的分裂没有引起父节点的分裂
{
/*
找到插入点之后,直接把后面的部分往后移一位
然后把新加入的子树加入进去
*/
rNode.parent=parent;
int index=0;
for(;index<parent->child_size;index++)
if((parent->child_list)[index]==&lNode)
break;
parent->child_size=parent->child_size+1;
for(int i=parent->child_size-1;i>index+1;i--)
{
parent->child_list[i]=parent->child_list[i-1];
}
parent->child_list[index+1]=&rNode;
//下面更新父节点的索引数组
/*其实不必把所有的索引值重新计算一遍
只需要计算加入的新节点的索引值即可
for(int i=parent->child_size-1;i>0;i--)
parent->index_key[i-1]= \
get_min_value(*(parent->child_list[i]));*/
for(int i=parent->child_size-2;i>index;i--)
parent->index_key[i]=parent->index_key[i-1];
parent->index_key[index]=get_min_value(rNode);
}
}
else  //如果原节点的父节点为空,那么说明要分裂的是根节点
{
BTree* newRoot=new BTree;
newRoot->parent=NULL;
newRoot->child_size=2;
newRoot->is_leaf=false;
newRoot->child_list[0]=&lNode;
newRoot->child_list[1]=&rNode;
int index_key_value=get_min_value(rNode);
newRoot->index_key[0]=index_key_value;
lNode.parent=newRoot;
rNode.parent=newRoot;
root=newRoot;
}
}
/*
从叶子删除某一个元素
当数据项所在的叶子已经具有最低数量的数据项,那么就要进行数据项的合并
*/
void BTree::remove(int data)
{
if(!root)
return;
/*首先找到待删除元素所在的叶子*/
BTree* p=root;
while(!p->is_leaf)
{
int i=0;
for(;i<p->child_size-1;i++)
if(data<p->index_key[i])
break;
p=p->child_list[i];
}
if(p->is_leaf)
{
int remove=array_remove(p->elem_data,data,p->elem_size);
if(remove==-1) //删除失败,元素不在树中,直接返回即可
return; 
p->elem_size=p->elem_size-1;
/*
如果删除之后,树叶中元素的个数太少,就需要进行合并
如果被删除的叶子节点是根节点,并且删除之后没有元素
那么就需要将树删除
*/
if(!(p->parent))
{
if(p->elem_size==0)
{
delete root;
root=NULL;
return;
}
}
else if(p->elem_size<(L/2+1))
{
node_merge(*p);
}
else   /*更新父节点的索引信息*/
{
int index=0;
for(;index<p->parent->child_size;index++)
if(p==p->parent->child_list[index])
break;
if(index>0)
p->parent->index_key[index-1]=
get_min_value(*p);
}

}
else   //find p, but not a leaf
{
cout<<"something is wrong..."<<endl;
}
}
/*
找出要与之合并的节点。查找规则如下
如果这个节点是父节点的第一个子节点,那么就找后一个节点合并
如果这个节点是父节点的最后一个子节点,那么就找前一个节点合并
如果是中间节点,就尽量找能合并后还是为两个子节点的那个节点;如果
不满足上述条件,那么就找前一个节点

检查要合并的节点,如果是根节点。则检查子树个数是否小于2,如果小于,就让根指向其子树,删除之前的根节点;如果不小于,则不做处理
如果不是根节点,那么确认此节点是否需要合并(叶子或非叶子)。需要合并,则寻找合适的兄弟节点与之合并
对于根节点直接就为叶子节点的情况,在调用者那里已经被排除,所以可以不用考虑
寻找兄弟节点的规则如上说明
*/
void BTree::node_merge(BTree& node)
{
/*如果这个叶子节点已经是根节点,那么需要检查剩下的子节点个数是否不小于2*/
if(node.parent==NULL)
{
if(node.child_size<2)
{
BTree* temp=root;
root=root->child_list[0];
root->parent=NULL;
temp->is_leaf=true;
delete temp;
}
}
else  //待合并的节点非根节点
{
BTree* parent=node.parent;
int index=0;
int merge_index=-1;
for(;index<parent->child_size;index++)
if(parent->child_list[index]==&node)
break;
if(index==0)
merge_index=index+1;
else if(index==parent->child_size-1)
merge_index=index-1;
if(node.is_leaf) //待合并的节点是叶子节点
{
if(node.elem_size<(L/2+1)) 
{
if(-1==merge_index)
{
BTree* temp=parent->child_list[index+1];
/*
尽量找一个合并之后
不会对父节点的子节点数造成变动的节点来合并
*/
if((node.elem_size+temp->elem_size)>L)
merge_index=index+1;
else
merge_index=index-1;
}
BTree* merge_node=parent->child_list[merge_index];
int total_num=merge_node->elem_size+node.elem_size;
if(total_num>L) //未对父类的子节点数造成影响
{
/*
先暂存所有的元素,然后重新分配
最后更新父节点的索引表
*/
int* p=new int[total_num];
int minIndex=
index<merge_index?index:merge_index;
int bigIndex=
minIndex==index?merge_index:index;
int tempNum=
minIndex==index? node.elem_size: \
merge_node->elem_size;
for(int i=0;i<tempNum;i++)
p[i]=parent->child_list[minIndex] \
->elem_data[i];
for(int i=0;i<total_num-tempNum;i++)
p[tempNum+i]=
parent-> \
child_list[bigIndex] -> \
elem_data[i];
/*开始重新分配元素*/
int num=total_num/2;
parent->child_list[minIndex]->elem_size=num;
parent->child_list[bigIndex]->elem_size= \
total_num-num;
for(int i=0;i<num;i++)
parent->child_list[minIndex] \
->elem_data[i]=p[i];
//parent->child_list[minIndex]->parent=parent;
for(int i=0;i<total_num-num;i++)
parent->child_list[bigIndex] \
->elem_data[i]=p[num+i];
//parent->child_list[bigIndex]->parent=parent;
//更新父节点的索引表
if(index>0)
parent->index_key[index-1]=
get_min_value(node);
if(merge_index>0)
parent->index_key[merge_index-1]=
get_min_value(*merge_node);
}
else  //使得父节点的子节点数减一
{
/*合并节点,然后向上检查父节点*/
for(int i=0;i<node.elem_size;i++)
{
array_insert( \
merge_node->elem_data, \
node.elem_data[i],  \
merge_node->elem_size);
merge_node->elem_size= \
merge_node->elem_size+1;
}
/*移动父节点的指向子树的指针数组*/
for(int i=index;i<parent->child_size-1;i++)
parent->child_list[i]=  \
parent->child_list[i+1];
parent->child_size=parent->child_size-1;
/*更新父节点的索引数组*/
for(int i=0;i<parent->child_size-1;i++)
parent->index_key[i]= \
   get_min_value( \
*(parent->child_list[i+1]));
node_merge(*parent);
}
}
}
else //待合并的节点为非叶子节点
{
if(node.child_size<(M/2+1))
{
if(-1==merge_index)
{
BTree* temp=parent->child_list[index+1];
/*
尽量找一个合并之后
不引起父节点子树个数变动的子节点合并
*/
if((temp->child_size+node.child_size)>M)
merge_index=index+1;
else
merge_index=index-1;
}
BTree* merge_node=parent->child_list[merge_index];
int total_num=
node.child_size+merge_node->child_size;
int minIndex=index<merge_index?index:merge_index;
int bigIndex= minIndex==index?merge_index:index;
int tempNum=
minIndex==index?node.child_size \
:merge_node->child_size;
/*
用一个临时数组,所有的指向子树的指针按顺序保存起来
*/
BTree** p=new BTree*[total_num];
for(int i=0;i<tempNum;i++)
p[i]=parent->child_list[minIndex] \
->child_list[i];
for(int i=0;i<total_num-tempNum;i++)
p[tempNum+i]=parent-> \
child_list[bigIndex]-> child_list[i];
if(total_num>M) //不会造成子节点数的变化
{
/*开始重新分配节点*/
int num=total_num/2;
parent->child_list[minIndex]->child_size=num;
parent->child_list[bigIndex]->child_size= \
total_num-num;
for(int i=0;i<num;i++)
{
parent->child_list[minIndex] \
->child_list[i]=p[i];
p[i]->parent=parent->child_list[minIndex];
}
for(int i=0;i<total_num-num;i++)
{
parent->child_list[bigIndex] \
->child_list[i]=p[num+i];
p[num+i]->parent=parent->child_list[bigIndex];
}

/*重新设置每个节点的索引值*/
for(int i=0;i<node.child_size-1;i++)
node.index_key[i]=get_min_value(*( \
node.child_list[i+1]));
for(int i=0;i<merge_node->child_size-1;i++)
merge_node->index_key[i]=  \
get_min_value(*( merge_node-> \
child_list[i+1]));
/*重新分配父节点的索引节点*/
if(index>0)
parent->index_key[index-1]=
get_min_value(node);
if(merge_index>0)
parent->index_key[merge_index-1]=
get_min_value(*merge_node);
}
else //会使得父节点的子树个数减一
{
/*指向子树的指针的合并,合并的时候注意要修改每个被合并的子树所要指向的父节点*/
merge_node->child_size=total_num;
for(int i=0;i<total_num;i++)
{
merge_node->child_list[i]=p[i];
p[i]->parent=merge_node;
}
/*更新合并后的新树的索引值*/
for(int i=0;i<total_num-1;i++)
merge_node->index_key[i]=
get_min_value(  \
*(merge_node->child_list[i+1]));
/*更新父节点的信息*/
for(int i=index;i<parent->child_size-1;i++)
parent->child_list[i]=
parent->child_list[i+1];
parent->child_size=parent->child_size-1;
//父节点的索引信息更新
for(int i=0;i<parent->child_size-1;i++)
parent->index_key[i]=
get_min_value( \
*(parent->child_list[i+1]));
node_merge(*parent);

}
delete[] p;
}
}
}
}
/*
不必考虑溢出,调用者保证数组元素的个数
删除元素成功,就返回0(元素在数组中)
否则返回-1(元素不在数组中)
*/
int BTree::array_remove(int* array,int data,int size)
{
int index=0;
for(;index<size;index++)
if(data==*(array+index))
break;
if(index==size)
return -1;
for(int i=index;i<size-1;i++)
array[i]=array[i+1];
return 0;
}
/*
取得某一颗子树中元素的最小值,在父节点构造索引值的时候需要
*/
int BTree::get_min_value(const BTree& child)
{
if(child.is_leaf)
return child.elem_data[0];
else
{
return get_min_value(*(child.child_list[0]));
}
}
int BTree::get_size(const BTree& node)
{
if(node.is_leaf)
return node.elem_size;
else
return node.child_size;
}
/*
主要是检查子节点和父节点之间的相互指向是否正确
*/
bool check_tree(BTree* root)
{
bool result=true;
if(root&&!(root->is_leaf))
{
for(int i=0;result&&(i<root->child_size);i++)
{
result=result&&(root==root->child_list[i]->parent);
result=result&&check_tree((root->child_list)[i]);
}
}
return result;
}
/*友元函数,将数按照层次结构打印出来*/
ostream& operator<<(ostream& os,BTree tree)
{
if(!(tree.root))
return os;
vector<BTree*> outQueue;
outQueue.push_back(tree.root);
vector<BTree*> tempQueue=outQueue;
int level=0;
while(outQueue.size()>0)
{
int i=0;
tempQueue=outQueue;
outQueue.clear();
os<<"level="<<level<<" -->";
while(i<tempQueue.size())
{
BTree* node=tempQueue[i];
i++;
if(!node->parent) //如果是根节点
os<<"[root]: ";
if(node->is_leaf)  //如果是叶子节点就输出元素值
{
os<<"[leaf]: ";
os<<"[ ";
for(int j=0;j<(node->elem_size);j++)
os<<(node->elem_data)[j]<<" ";
os<<"] ";
}
//如果是非叶子节点就输出索引值,并把子数加入到队列中
else  
{
os<<"[node]: ";
os<<"[ ";
int j=0;
//cout<<"node child_size="<<node->child_size<<endl;
for(;j<(node->child_size-1);j++)
{
//cout<<"index="<<j<<endl;
os<<((node->index_key)[j])<<" ";
//cout<<"run here 1"<<endl;
outQueue.push_back((node->child_list)[j]);
//cout<<"run here 2"<<endl;
}
outQueue.push_back((node->child_list)[j]);
os<<"] ";
}
}
tempQueue.clear();
level++;
os<<endl;
} //end of outside while loop
return os;
}
int main()
{
BTree treeA;
BTree treeB;
BTree treeC;
BTree treeD;
BTree treeE;
int test_dataA[]={2,8,18,26,35,4,10,20,28,36,6,12,22,30,37,14,24,31,38,16,32,39,25,74,65,74,37,98,79,62,143,213,421,3421,532,3455};
int test_dataB[]={2,1,5,7,3,9,21,32,17,54,12,6,8};
int test_dataC[]={3,4,6,12,32,45,75,47,13,123,67,94,2,5,17,21};
int test_dataD[]={2,8,18,26,35,4,10,20,28,36,6,12,22,30,37,14,24};
int test_dataE[]={12,22,30,37,14,24,31,38,16,32,39,25,74,65,74,37,98,79,62,143,213};
treeA.insert(test_dataA,sizeof(test_dataA)/sizeof(int));
treeB.insert(test_dataB,sizeof(test_dataB)/sizeof(int));
treeC.insert(test_dataC,sizeof(test_dataC)/sizeof(int));
treeD.insert(test_dataD,sizeof(test_dataD)/sizeof(int));
treeE.insert(test_dataE,sizeof(test_dataE)/sizeof(int));
cout<<"...........treeA..............."<<endl;
cout<<treeA<<endl;
cout<<"...........treeB..............."<<endl;
cout<<treeB<<endl;
cout<<"...........treeC..............."<<endl;
cout<<treeC<<endl;
cout<<"...........treeD..............."<<endl;
cout<<treeD<<endl;
cout<<"...........treeE..............."<<endl;
cout<<treeE<<endl;
/*
BTree treeF;
srand(time(0));
for(int i=0;i<500;i++)
{
int value=rand()%1000+1;
treeF.insert(value);
}
cout<<treeF<<endl;
*/
/**/
for(int i=0;i<sizeof(test_dataC)/sizeof(int);i++)
{
int value=test_dataC[i];
cout<<"............remove data [ "<<value<<" ]............."<<endl;
treeC.remove(value);
cout<<"............after remove data [ "<<value<<" ]..............."<<endl;
cout<<treeC<<endl;
}
cout<<"before remove check result "<<check_tree(treeA.getRoot())<<endl;
for(int i=0;i<sizeof(test_dataA)/sizeof(int);i++)
{
int value=test_dataA[i];
cout<<"............remove data [ "<<value<<" ]............."<<endl;
treeA.remove(value);
cout<<"............after remove data [ "<<value<<" ]..............."<<endl;
cout<<"........check tree after remove "<<check_tree(treeA.getRoot())<<"......"<<endl;
if(!check_tree(treeA.getRoot()))
break;
cout<<treeA<<endl;
}
}

几张运行效果如下图所示(数据太多,没法全部截图。感兴趣的朋友可以直接编译运行,或者修改测试数据)

B-Tree的C++实现_第1张图片

B-Tree的C++实现_第2张图片

B-Tree的C++实现_第3张图片

B-Tree的C++实现_第4张图片

B-Tree的C++实现_第5张图片

B-Tree的C++实现_第6张图片

你可能感兴趣的:(tree,B树,B)