二叉查找树和平衡二叉树都是典型的二叉查找树结构,其查找的时间复杂度与树的高度相关,降低树的高度自然对查找效率有所帮助,B树正是这样的树。
一棵m阶B树,或为空树,满足以下特性:
这里我发现跟定义跟算法导论一书中似乎不一样,有待研究呀!
#define M 7 //阶数
#define SUCCESS 1
#define ERROR 0
const int MIN = ceil(M/2.0)-1;
const int MAX = M-1;
typedef struct BTNode{
int size; // 当前关键字大小
int keys[M+1]; // 关键字数组,0号单元未使用,按升序排序
struct BTNode *parent; // 双亲结点指针
struct BTNode *childs[M+1]; // 孩子结点数组
}BTNode,*BTree;
typedef struct {
BTree btree;// 指向找到的结点
int i; // 1<= i <= M,在结点中关键字数组的位置
int tag; // 标记查找是否成功,1为成功,2为失败
}Result;
基本的接口有查找、插入、删除,下面分别讲解
由B树的定义,孩子结点指针Ai中的关键字均小于Ki,大于Ki-1。先从根结点开始查找,若找到要查找的关键字,则查找成功;否则则根据比较进入孩子结点Ai查找,如此反复,直到找到或者查找到叶子结点为止。
下面给出相关代码:
// 比较关键字a与b的大小
// 1: a>b
// 0: a==b
// -1 a<b
int CompareTo(int a, int b){
return a>b? 1 : a == b ? 0 : -1;
}
// 在结点t中查找关键字key
// 返回 的index表示keys[index]之前的关键字都小于key
int SearchKey(BTree &t,int key){
int index = 1; // 从1开始
// 保证index小于关键字数组的大小,同时保证keys[index]之前的关键字都小于key
while( index <= t->size && CompareTo(key,t->keys[index]) == 1 )
index++;
return index;
}
// 查找关键字key
// 返回结果res 查找成功则返回位置,否则则返回待插入位置
void Search(BTree &t,int key,Result &res){
BTree p,q; // p表示当前节点,q表示上一个结点
p = t;
q = NULL;
int isFound = 0; // 表示是否查找成功,0为不成功,1为成功
int index = 0;
while(p!=NULL&&isFound==0){
// 在当前节点查找
index = SearchKey(p,key);
if( index <= p->size && CompareTo(key, p->keys[index]) == 0 ) isFound = 1;
else{
q = p;
p = p->childs[index-1];
}
}
if(isFound == 1){
res.btree = p;
res.tag = isFound;
res.i = index;
}else{
// 查找不成功,返回key的插入位置
res.btree = q; // 待插入结点
res.i = index; // 待插入的位置
res.tag = isFound;
}
}
利用前述的查找结果,若找到,则直接返回,否则,根据待插入位置插入。插入后,若其关键字总数size为达到m,结束,否则分裂结点
分裂结点:
下面是代码,有点多,不过逻辑还是挺清晰的,而且还有注释:
// 创建以t为根的根结点
void NewRoot(BTree &t,BTree p,int key,BTree ap){
t = (BTNode*)malloc(sizeof(BTNode));
t->keys[1] = key;
t->childs[0] = p;
t->childs[1] = ap;
t->size = 1;
t->parent = NULL;
if(p!=NULL) p->parent = t;
if(ap!=NULL) ap->parent = t;
}
// 将关键字插入结点t的index位置,ap为其子结点
void InsertToNode(BTree &t,int key,int index,BTree &ap){
int n = t->size;
for(int i = n; i >= index; i--){
t->keys[i+1] = t->keys[i];
t->childs[i+1] = t->childs[i];
}
t->keys[index] = key;
t->childs[index] = ap;
if( ap!=NULL ) ap->parent = t;
t->size++;
}
// 将结点t分裂成两个结点,前一半保留,后一半移入新结点ap
void Split(BTree &t,int s,BTree &ap){
int n = t->size;
ap = (BTNode*)malloc(sizeof(BTNode));
int i, j;
for( i = s+1, j = 1; i <= n; i ++, j++ ){
ap->keys[j] = t->keys[i];
}
for( i = s, j = 0; i <= n; i++, j++){
ap->childs[j] = t->childs[i];
if(ap->childs[j]!=NULL) ap->childs[j]->parent = ap;
}
ap->size = n - s;
ap->parent = t->parent;
t->size = s - 1;
}
// 在B树中插入关键字key
// q:B树上结点,又查找失败得到,待插入位置为q->keys[index-1]和q->keys[index]之间
void InsertToTree(BTree &t,int key,BTree q,int index){
int mFinished = 0; // 标记是否插入完成
int isNeedNewRoot = 0; // 标记是否需要创建新的根结点
int mKey = key; // 关键字
if( q == NULL )
NewRoot(t,NULL,mKey,NULL);
else{
BTree ap = NULL;
while(isNeedNewRoot == 0 && mFinished == 0){
InsertToNode(q,mKey,index,ap);
if(q->size < M)
mFinished = 1;
else{
// 需要分裂结点q
int s = (M+1)/2;
mKey = q->keys[s];
Split(q,s,ap);
if(q->parent != NULL){
// 将结点被分裂出来的关键字mKey插入父结点中
q = q->parent;
index = SearchKey(q,mKey);
}else{
// 此时q原本是根结点,被分裂成结点q和ap
isNeedNewRoot = 1;
}
}
}
if(isNeedNewRoot == 1)
NewRoot(t,q,mKey,ap);
}
}
// 在B树中插入关键字key
int Insert(BTree &bt,int key){
Result result;
Search(bt,key,result);
if( result.tag == ERROR ){
InsertToTree(bt,key,result.btree,result.i);
return SUCCESS;
}
return ERROR;
}
B树的删除操作就有点复杂,要分多种情况考虑,最重要的还是对定义要有深刻的理解,特别是在删除过程,必须明确现在的B树是满足定义的,因为在插入跟删除都不断维持着B树的状态,所以不要做无谓的判断,这一点我自己确实做得不好
思路:
先通过查找操作查找待删除关键字位置,再根据所在结点是否最下层非终端结点处理
调整过程:
当最下层非终端结点被删除某关键字后,需要进行调整
下面是代码,代码有点长,最重要还是思路:
// 移除结点t中pos位置的关键字 和child[pos-1]
void RemoveKey(BTree &t,int pos){
int size = t->size;
int i;
for(i = pos; i < size; i ++){
t->keys[i] = t->keys[i+1];
// 孩子结点为空,可以不用管 ,
// 不能不管 , 可能在Restore2中调用非终端结点的
// t->childs[i] = t->childs[i+1]; // 但是这样不行,另外下面方法调用
t->childs[i-1] = t->childs[i]; // pos >= 1
}
t->childs[i-1] = t->childs[i];
t->size --;
}
// 查找父结点parent中子结点t的位置,查找不到则返回-1
int SearchChildPos(BTree parent,BTree t){
if(t == NULL){
return -1;
}
int size = parent->size;
for(int i=0; i <= size; i ++){
if(parent->childs[i] == t){
return i;
}
}
return -1;
}
// 删除树上结点t的pos个位置
void DeleteBTree(BTree &t,int pos){
if( t==NULL ) return ;
if(t->childs[pos] != NULL){ // 不是最下层终端结点
// 查找孩子子树最下层非终端结点最小关键字
BTree child = t->childs[pos];
while( child->childs[0] != NULL){
child = child->childs[0];
}
int key = child->keys[1];
t->keys[pos] = key;
// 调用delete
DeleteBTree(child,1);
} else{ // 下层终端结点
RemoveKey(t,pos);
if( t->size < MIN ){
Restore(t,pos);
}
}
}
// 删除B树t上的关键字key
int Remove(BTree &t,int key){
Result res;
Search(t,key,res);
if(res.tag == 1){
DeleteBTree(res.btree,res.i);
return 1;
}else{
printf("无该关键字\n");
return 0;
}
}
// 查看兄弟结点是否富余
// 若富余,调整后返回 SUCCESS 1
// 否则 返回 ERROR 0
int Restore2(BTree &t){
if( t == NULL ) return ERROR;
if( t->size >= MIN ) return SUCCESS;
BTree parent = t->parent;
if( parent == NULL ) return ERROR;
int sizeOfParent = parent->size;
int posInParent;
if((posInParent = SearchChildPos(parent,t)) == -1 ) return ERROR;
if( posInParent > 0 && parent->childs[posInParent-1] != NULL && parent->childs[posInParent-1]->size > MIN ){ // 查找左兄弟,仅仅左兄弟
// 如果有,则获取左兄弟最大关键字
BTree lbt = parent->childs[posInParent-1];
int leftMaxKey = lbt->keys[lbt->size];
// 注意这里移动后,结点的父结点的处理
InsertToNode(t,parent->keys[posInParent],1,t->childs[0]);
t->childs[0] = lbt->childs[lbt->size];
if(t->childs[0]) t->childs[0]->parent = t;
lbt->childs[lbt->size] = NULL;
lbt->size--;
parent->keys[posInParent] = leftMaxKey;
return SUCCESS;
}else if( posInParent < sizeOfParent && parent->childs[posInParent+1] != NULL && parent->childs[posInParent+1]->size > MIN ){// 查找右兄弟
// 如果有,则获取右兄弟最小关键字
BTree rbt = parent->childs[posInParent+1];
int rightMinKey = rbt->keys[1];
// 注意这里移动后,结点的父结点的处理
InsertToNode(t,parent->keys[posInParent+1],t->size+1,rbt->childs[0]);
RemoveKey(rbt,1);
// 插入父结点
parent->keys[posInParent+1] = rightMinKey;
return SUCCESS;
}
return ERROR;
}
// 与兄弟结点合并操作,操作后递归对父结点进行调整
void Restore3(BTree &t){
if( t == NULL ) return ;
if(t->size>=MIN){
return;
}
BTree parent = t->parent;
if( parent == NULL ){
// ????为什么这样不行
// t = t->childs[0];
if( t->size > 0 ){
return ;
}
BTree tt = t->childs[0];
if( tt == NULL ) return ;
if(t->childs[0]) t->childs[0]->parent = NULL;
t->childs[0] = NULL;
int size = tt->size;
for(int i = 1; i <= size; i ++){
t->keys[i] = tt->keys[i];
}
for(int i = 0; i <= size; i++){
t->childs[i] = tt->childs[i];
if( t->childs[i] ) t->childs[i]->parent = t; // T^T ...
}
t->size = size;
t->parent = NULL;
return ;
}
int posInParent;
if((posInParent = SearchChildPos(parent,t)) == -1)
return ;
int sizeOfParent = parent->size;
int mIsFinished = 0;
// 与父结点和左结点结合
if( posInParent > 0 && mIsFinished == 0){
// .......................................可能刚刚好该结点为空 ,不用考虑T^T,还是去看看B树的性质吧。。
BTree lbt = parent->childs[posInParent-1];
if(lbt == NULL) {
lbt = (BTNode*)malloc(sizeof(BTNode));
if( lbt == NULL ) return;
lbt->parent = parent;
parent->childs[posInParent-1] = lbt;
lbt->size = 0;
}
if(lbt != NULL ){
lbt->keys[++lbt->size] = parent->keys[posInParent];
lbt->childs[lbt->size] = NULL; // ..........................因为lbt结点结合t结点,而t结点为终端结点,自然没有孩子
int i,j;
for(j = lbt->size+1, i = 1; i <= t->size;i++, j++){
lbt->keys[j] = t->keys[i];
// lbt->childs[j] = NULL;
}
for( int k = lbt->size, i = 0; i <= t->size; i ++, k++){
lbt->childs[k] = t->childs[i];
if(t->childs[i]) t->childs[i]->parent = NULL;
t->childs[i] = NULL;
if(lbt->childs[k]!=NULL) lbt->childs[k]->parent = lbt; // 居然还漏了 。。。
}
lbt->size+=t->size;
t->size = 0;
}
// 移除父结点中所结合的结点 、移除孩子
if(parent->childs[posInParent]) parent->childs[posInParent]->parent = NULL;
parent->childs[posInParent] = NULL;
for(int i = posInParent; i < parent->size; i ++){
parent->keys[i] = parent->keys[i+1];
parent->childs[i] = parent->childs[i+1];
}
parent->size --;
mIsFinished = 1;
}
// 与父结点和右结点结合
if( posInParent < sizeOfParent && mIsFinished == 0 ){
BTree rbt = parent->childs[posInParent+1];
// 应该移入有结点中,为什么?? 因为t为终端结点
t->keys[++t->size] = parent->keys[posInParent+1];
t->childs[t->size] = NULL;
int i, j;
if(rbt!=NULL){
for(i = 1, j = t->size+1; i <= rbt->size; i ++,j ++){
t->keys[j] = rbt->keys[i];
}
for( int k = t->size, i = 0; i <= rbt->size; i ++, k ++){
t->childs[k] = rbt->childs[i];
if(rbt->childs[i]) rbt->childs[i]->parent = NULL;
rbt->childs[i] = NULL;
if(t->childs[k]!=NULL) t->childs[k]->parent = t; // 居然还漏了 。。。
}
t->size += rbt->size;
rbt->size = 0;
}
// 移除父结点中所结合的结点 、移除孩子
// RemoveKey(parent,posInParent);
if(parent->childs[posInParent+1]) parent->childs[posInParent+1]->parent = NULL;
parent->childs[posInParent+1] = NULL;
for(i = posInParent+1; i < parent->size; i ++){
parent->keys[i] = parent->keys[i+1];
parent->childs[i] = parent->childs[i+1];
}
parent->size --;
mIsFinished = 1;
// 不能直接调用下面方法删除,因为该结点不是终端结点时,会获取最小结点替代
// int kk = parent->keys[posInParent+1];
// DeleteBTree(parent,posInParent+1);
}
// 调整父结点
Restore2(parent);
Restore3(parent);
}
// 调整
void Restore(BTree &t,int pos){
if( Restore2(t) == ERROR ) {
Restore3(t);
}
}
这个B树的抽象数据类型是之前写的,都是泪啊,定义不熟悉,而且代码不严谨,在移动或者调整的时候,孩子结点经常忘了其父结点等等错误,这就有了我当时两三天的调试T^T,醉了。
理解好定义,写代码之前一定思考
这段时间考试,各种预习,关于编程,所以只看了下《Android开发艺术探索》,简单的就直接划书,难的又看不懂,唉,博客总结也落下了
最后如果本文有哪个地方不足或者错误,还请指正!