【树】树算法之 B 树

树算法之 B 树

罗朝辉(http://blog.csdn.net/kesalin

转载请注明出处

B树是一种被设计成专门存储在磁盘上的平衡查找树。因为磁盘的操作速度要大大慢于随机存取存储器,所以在分析B树的性能时,不仅要看动态集合操作花了多少计算时间,还要看执行了多少次磁盘存储操作。B树与红黑树(下一篇介绍)类似,但在降低磁盘I/O操作次数方面要更好一些。许多数据库系统就使用B树或B树的变形来存储信息,想象一下一棵每个节点包含1001key的高度为2B树能容纳多少数据啊,而在内存中我们只存储了一个节点,在需要的时候再从磁盘中读取所需的节点。

B树红黑树比较:

B树的节点有很多子女,从几个到几千,而红黑树只有左右两个;一棵含有n个节点的B树与红黑树的高度均为O(lgn),只不过B树的分支较多,因此高度一般要少于红黑树。

B树到底是怎样一棵树呢,下面来看定义:

1),每个节点有如下域:
A),keyNum,节点中存储的关键字的个数。
B),keyNum个以非降序次序排列的关键字key[0...keyNum-1]
C),isLeaf,判断是否是叶子节点还是内节点的标志。

2),每个内节点还包含keyNum+1个指向其子女的指针child[i](i>=0&&i<=keyNum)

3),各个关键字key[i]对存储在各子树中的关键字范围加以分隔:即key[i]大于等于其左侧子树中的所有关键字,而小于等于其右侧子树中的所有关键字。

4),每个叶节点具有相同的深度,即均为树的高度h

5)每一个节点能包含的关键字有一个上限和下限。这些界限可以用一个称作B树的最小度数的固定整数T>=2来表示。
A),每个非根的节点必须至少有T-1个关键字。每个非根的内节点至少有T个子女。如果树是非空的,则根节点至少包含一个关键字。
B),每个节点可包含至多2T-1个关键字。所以一个内节点至多有2T个子女。当一个节点正好有2T-1个关键字时,我们就说这个节点是满的。

T等于2 时的B树是最简单的。这时每个内节点有2个或3个或4个子女,这种B树也被称作为2-3-4树。当然在实际应用中T的取值比这个大得多。

下面我实现了T默认等于2B树:

// 定义B树的最小度数
// 每个节点中关键字的最大数目BTree_N=2*BTree_T-1
#define BTree_T2
#define BTree_N(BTree_T*2-1)

struct BTNode {
intkeynum;//结点中关键字的个数,keynum<=BTree_N
intkey[BTree_N];//关键字向量为key[0..keynum-1]
BTNode*child[BTree_T*2];//孩子指针向量为child[0..keynum]
boolisLeaf;//是否是叶子节点的标志
}
;

typedefBTNode
* BTree; // B树的类型


void BTree_create(BTree * tree, const int * data, int length);

void BTree_destory(BTree * tree);

void BTree_insert(BTree * tree, int key);

void BTree_remove(BTree * tree, int key);

void BTree_print(BTreetree, int her = 1 );

// 在B树tree中查找关键字key,
// 成功时返回找到的节点的地址及key在其中的位置*pos
// 失败时返回NULL及查找失败时扫描到的节点位置*pos
//
BTNode * BTree_search( const BTreetree, int key, int * pos);


下面来看具体的接口实现:

#define max(a,b)(((a)>(b))?(a):(b))

// #defineDEBUG_TREE

#ifdefDEBUG_TREE
#define debug_print(fmt,)printf(fmt,##__VA_ARGS__)
#else
#define debug_print(fmt,)
#endif

// 模拟向磁盘写入节点
void disk_write(BTNode * node)
{
printf(
"向磁盘写入节点/n");
}


// 模拟从磁盘读取节点
void disk_read(BTNode ** node)
{
printf(
"从磁盘读取节点/n");
}


// 按层次打印B树
void BTree_print(BTreetree, int her)
{
inti;
BTNode
*node=tree;

if(node){
printf(
"第%d层,%dnode:",her,node->keynum);

for(i=0;i<node->keynum;++i){
printf(
"%c",node->key[i]);
}


printf(
"/n");

++her;
for(i=0;i<=node->keynum;i++){
if(node->child[i]){
BTree_print(node
->child[i],her);
}

}

}

else{
printf(
"树为空。/n");
}

}


// 将非满的节点与其第index个满孩子节点合并
// parent是一个非满的父节点
// node是tree孩子表中下标为index的孩子节点,且是满的
void BTree_split_child(BTNode * parent, int index,BTNode * node)
{
assert(parent
&&node);

inti;

//创建新节点,存储node中后半部分的数据
BTNode*newNode=(BTNode*)calloc(sizeof(BTNode),1);
if(!newNode){
printf(
"Error!outofmemory!/n");
return;
}


newNode
->isLeaf=node->isLeaf;
newNode
->keynum=BTree_T-1;

//拷贝node后半部分关键字
for(i=0;i<newNode->keynum;++i){
newNode
->key[i]=node->key[BTree_T+i];
node
->key[BTree_T+i]=0;
}


//如果node不是叶子节点,拷贝node后半部分的孩子节点
if(!node->isLeaf){
for(i=0;i<BTree_T;i++){
newNode
->child[i]=node->child[BTree_T+i];
node
->child[BTree_T+i]=NULL;
}

}


//将node分裂出newNode之后,里面的数据减半
node->keynum=BTree_T-1;

//调整父节点
for(i=parent->keynum;i>index;--i){
parent
->child[i+1]=parent->child[i];
}


parent
->child[index+1]=newNode;

for(i=parent->keynum-1;i>=index;--i){
parent
->key[i+1]=parent->key[i];
}


parent
->key[index]=node->key[BTree_T-1];
++parent->keynum;

//清除node中的中后部数据
//可以不处理,因为是通过keynum控制访问的
//for(i=BTree_T-1;i<BTree_N;++i){
//node->key[i]=0;
//node->child[i+1]=NULL;
//}

//写入磁盘
disk_write(parent);
disk_write(newNode);
disk_write(node);
}


void BTree_insert_nonfull(BTNode * node, int key)
{
assert(node);

inti;

//节点是叶子节点,直接插入
if(node->isLeaf){
i
=node->keynum-1;
while(i>=0&&key<node->key[i]){
node
->key[i+1]=node->key[i];
--i;
}


node
->key[i+1]=key;
++node->keynum;

//写入磁盘
disk_write(node);
}


//节点是内部节点
else{
//查找插入的位置
i=node->keynum-1;
while(i>=0&&key<node->key[i]){
--i;
}


++i;

//从磁盘读取孩子节点
disk_read(&node->child[i]);

//如果该孩子节点已满,分裂调整值
if(node->child[i]->keynum==BTree_N){
BTree_split_child(node,i,node
->child[i]);

if(key>node->key[i]){
++i;
}

}


BTree_insert_nonfull(node
->child[i],key);
}

}


void BTree_insert(BTree * tree, int key)
{
BTNode
*node;
BTNode
*root=*tree;

//树为空
if(NULL==root){
root
=(BTNode*)calloc(sizeof(BTNode),1);
if(!root){
printf(
"Error!outofmemory!/n");
return;
}

root
->isLeaf=true;
root
->keynum=1;
root
->key[0]=key;

*tree=root;

//写入磁盘
disk_write(root);

return;
}


//节点已满,需要进行分裂调整
if(root->keynum==BTree_N){
//产生新节点当作根
node=(BTNode*)calloc(sizeof(BTNode),1);
if(!node){
printf(
"Error!outofmemory!/n");
return;
}


*tree=node;
node
->isLeaf=false;
node
->keynum=0;
node
->child[0]=root;

BTree_split_child(node,
0,root);

BTree_insert_nonfull(node,key);
}


//节点未满,在当前节点中插入key
else{
BTree_insert_nonfull(root,key);
}

}



// 对tree中的节点node进行合并孩子节点处理
// 注意:孩子节点的keynum必须均已达到下限,即均等于BTree_T-1
// 将node中索引为index+1的孩子节点合并到索引为index的孩子节点中,
// 并将tree中索引为index的key下降到该节点中,调整相关的key和指针。
//
void BTree_merge_child(BTree * tree,BTNode * node, int index)
{
assert(tree
&&node&&index>=0&&index<node->keynum);

inti;

intkey=node->key[index];
BTNode
*prevChild=node->child[index];
BTNode
*nextChild=node->child[index+1];

assert(prevChild
&&prevChild->keynum==BTree_T-1
&&nextChild&&nextChild->keynum==BTree_T-1);

prevChild
->key[prevChild->keynum]=key;
prevChild
->child[prevChild->keynum+1]=nextChild->child[0];
++prevChild->keynum;

//合并
for(i=0;i<nextChild->keynum;++i){
prevChild
->key[prevChild->keynum]=nextChild->key[i];
prevChild
->child[prevChild->keynum+1]=nextChild->child[i+1];
++prevChild->keynum;
}


//在node中移除key以及指向后继孩子节点的指针
for(i=index;i<node->keynum-1;++i){
node
->key[i]=node->key[i+1];
node
->child[i+1]=node->child[i+2];
}


node
->key[node->keynum-1]=0;
node
->child[node->keynum]=NULL;
--node->keynum;

//如果根节点没有key了,删之,并将根节点调整为前继孩子节点。
if(node->keynum==0){
if(*tree==node){
*tree=prevChild;
}


free(node);
node
=NULL;
}


free(nextChild);
}


void BTree_remove(BTree * tree, int key)
{
//B-数的保持条件之一:
//非根节点的内部节点的关键字数目不能少于BTree_T-1

inti,j,index;
BTNode
*root=*tree;
BTNode
*node=root;
BTNode
*prevChild,*nextChild,*child;
intprevKey,nextKey;

if(!root){
printf(
"Failedtoremove%c,itisnotinthetree!/n",key);
return;
}


index
=0;
while(index<node->keynum&&key>node->key[index]){
++index;
}


//
//indexofkey:i-1ii+1
//+---+---+---+---+---+
//++A++
//+---+---+---+---+---+
///|/
//indexofC:i-1ii+1
///|/
//+---+---++---++---+---+
//++++++
//+---+---++---++---+---+
//prevChildchildnextChild

//Findthekey.
if(index<node->keynum&&node->key[index]==key){
//1,所在节点是叶子节点,直接删除
if(node->isLeaf){
for(i=index;i<node->keynum;++i){
node
->key[i]=node->key[i+1];
node
->child[i+1]=node->child[i+2];
}


--node->keynum;

if(node->keynum==0){
assert(node
==*tree);
free(node);
*tree=NULL;
}


return;
}


//2a,如果位于key前的子节点的key数目>=BTree_T,
//在其中找key的前驱,用前驱的key值赋予key,
//然后在前驱所在孩子节点中递归删除前驱。
elseif(node->child[index]->keynum>=BTree_T){
prevChild
=node->child[index];
prevKey
=prevChild->key[prevChild->keynum-1];
node
->key[index]=prevKey;

BTree_remove(
&prevChild,prevKey);
}


//2b,如果位于key后的子节点的key数目>=BTree_T,
//在其中找key的后继,用后继的key值赋予key,
//然后在后继所在孩子节点中递归删除后继。
elseif(node->child[index+1]->keynum>=BTree_T){
nextChild
=node->child[index+1];
nextKey
=nextChild->key[0];
node
->key[index]=nextKey;

BTree_remove(
&nextChild,nextKey);
}


//2c,前驱和后继都只包含BTree_T-1个节点,
//将key下降前驱孩子节点,并将后继孩子节点合并到前驱孩子节点,
//删除后继孩子节点,在node中移除key和指向后继孩子节点的指针,
//然后在前驱所在孩子节点中递归删除key。
elseif(node->child[index]->keynum==BTree_T-1
&&node->child[index+1]->keynum==BTree_T-1){
prevChild
=node->child[index];

BTree_merge_child(tree,node,index);

//在前驱孩子节点中递归删除key
BTree_remove(&prevChild,key);
}

}


//3,key不在内节点node中,则应当在某个包含key的子节点中。
//key<node->key[index],所以key应当在孩子节点node->child[index]中
else{
child
=node->child[index];
if(!child){
printf(
"Failedtoremove%c,itisnotinthetree!/n",key);
return;
}


if(child->keynum==BTree_T-1){
prevChild
=NULL;
nextChild
=NULL;

if(index-1>=0){
prevChild
=node->child[index-1];
}


if(index+1<=node->keynum){
nextChild
=node->child[index+1];
}


//3a,如果所在孩子节点相邻的兄弟节点中有节点至少包含BTree_t个关键字
//将node的一个关键字下降到child中,将相邻兄弟节点中一个节点上升到
//node中,然后在child孩子节点中递归删除key。
if((prevChild&&prevChild->keynum>=BTree_T)
||(nextChild&&nextChild->keynum>=BTree_T)){

if(nextChild&&nextChild->keynum>=BTree_T){
child
->key[child->keynum]=node->key[index];
child
->child[child->keynum+1]=nextChild->child[0];
++child->keynum;

node
->key[index]=nextChild->key[0];

for(j=0;j<nextChild->keynum-1;++j){
nextChild
->key[j]=nextChild->key[j+1];
nextChild
->child[j]=nextChild->child[j+1];
}

--nextChild->keynum;
}

else{
for(j=child->keynum;j>0;--j){
child
->key[j]=child->key[j-1];
child
->child[j+1]=child->child[j];
}

child
->child[1]=child->child[0];
child
->child[0]=prevChild->child[prevChild->keynum];
child
->key[0]=node->key[index-1];
++child->keynum;

node
->key[index-1]=prevChild->key[prevChild->keynum-1];

--prevChild->keynum;
}

}


//3b,如果所在孩子节点相邻的兄弟节点都只包含BTree_t-1个关键字,
//将child与其一相邻节点合并,并将node中的一个关键字下降到合并节点中,
//再在node中删除那个关键字和相关指针,若node的key为空,删之,并调整根。
//最后,在相关孩子节点中递归删除key。
elseif((!prevChild||(prevChild&&prevChild->keynum==BTree_T-1))
&&((!nextChild||nextChild&&nextChild->keynum==BTree_T-1))){
if(prevChild&&prevChild->keynum==BTree_T-1){

BTree_merge_child(tree,node,index
-1);

child
=prevChild;
}


elseif(nextChild&&nextChild->keynum==BTree_T-1){

BTree_merge_child(tree,node,index);
}

}

}


BTree_remove(
&child,key);
}

}


BTNode
* BTree_search( const BTreetree, int key, int * pos)
{
if(!tree){
returnNULL;
}


inti=0;

while(i<tree->keynum&&key>tree->key[i]){
++i;
}


//Findthekey.
if(i<tree->keynum&&tree->key[i]==key){
if(pos){
*pos=i;
}


returntree;
}


//tree为叶子节点,找不到key,查找失败返回
if(tree->isLeaf){
returnNULL;
}


//节点内查找失败,但tree->key[i-1]<key<tree->key[i],
//下一个查找的结点应为child[i]

//从磁盘读取第i个孩子的数据
disk_read(&tree->child[i]);

//递归地继续查找于树tree->child[i]
returnBTree_search(tree->child[i],key,pos);
}


void BTree_create(BTree * tree, const int * data, int length)
{
assert(tree);

inti;

#ifdefDEBUG_TREE
debug_print(
"/n开始创建B-树,关键字为:/n");
for(i=0;i<length;i++){
printf(
"%c",data[i]);
}

debug_print(
"/n");
#endif


for(i=0;i<length;i++){
#ifdefDEBUG_TREE
debug_print(
"/n插入关键字%c:/n",data[i]);
#endif
BTree_insert(tree,data[i]);

#ifdefDEBUG_TREE
BTree_print(
*tree);
#endif
}


debug_print(
"/n");
}


void BTree_destory(BTree * tree)
{
inti;
BTNode
*node=*tree;

if(node){
for(i=0;i<=node->keynum;i++){
BTree_destory(
&node->child[i]);
}


free(node);
}


*tree=NULL;
}

测试代码:

//==================================================================
//测试B 树
//==================================================================
voidtest_BTree_search(BTreetree,intkey)
{
intpos=-1;
BTNode
*node=BTree_search(tree,key,&pos);
if(node){
printf(
"在%s节点(包含%d个关键字)中找到关键字%c,其索引为%d/n",
node
->isLeaf?"叶子":"非叶子",
node
->keynum,key,pos);
}
else{
printf(
"在树中找不到关键字%c/n",key);
}
}

voidtest_BTree_remove(BTree*tree,intkey)
{
printf(
"/n移除关键字%c/n",key);
BTree_remove(tree,key);
BTree_print(
*tree);
printf(
"/n");
}

voidtest_btree()
{
constintlength=10;
intarray[length]={
'G','M','P','X','A','C','D','E','J','K',
//'N','O','R','S','T','U','V','Y','Z','F'
};

BTreetree
=NULL;
BTNode
*node=NULL;
intpos=-1;
intkey1='R';//inthetree.
intkey2='B';//notinthetree.

//创建
BTree_create(&tree,array,length);

printf(
"/n===创建B-树===/n");
BTree_print(tree);
printf(
"/n");

//查找
test_BTree_search(tree,key1);
printf(
"/n");
test_BTree_search(tree,key2);

//插入关键字
printf("/n插入关键字%c/n",key2);
BTree_insert(
&tree,key2);
BTree_print(tree);
printf(
"/n");

test_BTree_search(tree,key2);

//移除关键字
test_BTree_remove(&tree,key2);
test_BTree_search(tree,key2);

key2
='M';
test_BTree_remove(
&tree,key2);
test_BTree_search(tree,key2);

key2
='E';
test_BTree_remove(
&tree,key2);
test_BTree_search(tree,key2);

key2
='G';
test_BTree_remove(
&tree,key2);
test_BTree_search(tree,key2);

key2
='A';
test_BTree_remove(
&tree,key2);
test_BTree_search(tree,key2);

key2
='D';
test_BTree_remove(
&tree,key2);
test_BTree_search(tree,key2);

key2
='K';
test_BTree_remove(
&tree,key2);
test_BTree_search(tree,key2);

key2
='P';
test_BTree_remove(
&tree,key2);
test_BTree_search(tree,key2);

key2
='J';
test_BTree_remove(
&tree,key2);
test_BTree_search(tree,key2);

key2
='C';
test_BTree_remove(
&tree,key2);
test_BTree_search(tree,key2);

key2
='X';
test_BTree_remove(
&tree,key2);
test_BTree_search(tree,key2);

//销毁
BTree_destory(&tree);
}

测试结果:

===创建B ===

1层,1node:E

2层,1node:C

3层,1node:A

3层,1node:D

2层,1node:M

3层,3node:GJK

3层,2node:PX

从磁盘读取节点

从磁盘读取节点

在树中找不到关键字R

从磁盘读取节点

从磁盘读取节点

在树中找不到关键字B

插入关键字B

从磁盘读取节点

从磁盘读取节点

向磁盘写入节点

1层,1node:E

2层,1node:C

3层,2node:AB

3层,1node:D

2层,1node:M

3层,3node:GJK

3层,2node:PX

从磁盘读取节点

从磁盘读取节点

在叶子节点(包含2个关键字)中找到关键字B,其索引为1

.......


参考资料:

1,《算法导论》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值