B树的查找、插入、删除(附源代码)

B-Tree

Index


  • B-Tree搜索
  • B-Tree插入
    • 分裂节点
    • 插入节点
  • B-Tree删除
    • 合并节点
    • 删除节点

Basic


B-Tree有两个比较重要的性质:

  • 所有的leaf均在同一个level上
  • 除了root之外,其它所有node中所储存的数据至少为Minimum Degree - 1,至多为Minimum Degree * 2 - 1

两个结构定义,其中struct TreeNode定义了B-Tree中的一个node,而struct Tree则是包含了一个指向B-Tree的root的指针,相当于一个dummy node,设置这个结构的目的是为了可以将其作为参数传入函数中,以便可以在函数中修改root节点的值。

typedef struct TreeNode *PtrBTNode;
typedef struct TreeNode BTNode;
struct TreeNode{
    int Num;
    Bool IsLeaf;
    PtrElementType Key;
    PtrBTNode *Child;
};

typedef struct Tree *PtrBT;
struct Tree{
    PtrBTNode Root;
};

其他的一些定义和声明,其中ShiftKey函数用于对每个node中的Key数组进行部分左移或右移,Direction控制左移或右移方向(1-右移,0-左移),而BeginEnd参数则是用来传递被移动部分的最左端和最右端的元素下标,ShiftChild函数同理针对Child数组;GetIndex函数则是用来搜索在一个节点中大于或等于Val的最小元素的下标,如果均小于Val,就返回数组的大小(就是刚好越界了):

#define MinDegree 3

typedef int ElementType;
typedef int* PtrElementType;

typedef enum BoolType Bool;
enum BoolType{
    False = 0,
    True = 1
};

void ShiftKey(PtrElementType Key, Bool Direction, int Begin, int End){
    int i;
    
    if(True == Direction){
        for(i = End; i >= Begin; i--){
            Key[i + 1] = Key[i];
        }
    }
    else{
        for(i = Begin; i <= End; i++){
            Key[i - 1] = Key[i];
        }
    }
}

void ShiftChild(PtrBTNode *Child, Bool Direction, int Begin, int End){
    int i;
    
    if(True == Direction){
        for(i = End; i >= Begin; i--){
            Child[i + 1] = Child[i];
        }
    }
    else{
        for(i = Begin; i <= End; i++){
            Child[i - 1] = Child[i];
        }
    }
}

int GetIndex(PtrElementType Key, int Size, ElementType Val){
    int i;
    
    for(i = 0; i < Size; i++){
        if(Key[i] >= Val){
            break;
        }
    }
    
    return i;
}

void BTPrintTree(PtrBTNode Root){
    int i;
    
    if(NULL == Root){
        return;
    }
    
    putchar('[');
    for(i = 0; i < Root->Num; i++){
        printf("%d", Root->Key[i]);
        if(i != Root->Num - 1){
            putchar(' ');
        }
    }
    putchar(']');
    printf("%d", Root->IsLeaf);
    putchar('\n');
    
    for(i = 0; i <= Root->Num; i++){
        BTPrintTree(Root->Child[i]);
    }
}

void BTCreateTree(PtrBT T){
    int i;
    //A test case
    int a[] = {
   12,1,9,2,0,11,7,19,4,15,18,5,14,13,10,16,6,3,8,17,20,21,23};
    
    for(i = 0; i < 23; i++){
        BTInsert(T, a[i]);
        BTPrintTree(T->Root);
        printf("The End\n");
    }
}

Search


搜索节点的过程比较简单,下面是函数代码:

PtrBTNode BTSearch(PtrBTNode Root, ElementType Val, int* Index){
    int i;
    
    for(i = 0; i < Root->Num&&Val > Root->Key[i]; i++){
        ;
    }
    if(i < Root->Num&&Root->Key[i] == Val){
        *Index = i;
        return Root;
    }
    else if(True == Root->IsLeaf){
        return NULL;
    }
    else{
        return BTSearch(Root->Child[i], Val, Index);
    }
}

Insert


在B-Tree中插入一个值,为了不破坏B-Tree的第一个性质,必须将其插入在leaf中,但是又因为B-Tree同时具有第二个性质,所以要保证待插入值一定能成功插入,必须使得所有节点都是非满的,因此在某一个节点成为满节点时,就必须将该节点分裂为两个节点(分裂节点:将满节点分裂为两个包含Minimum Degree - 1个数据的节点,并将原来满节点中数据的中值插入原满节点的父节点中,同时在原满节点的父节点中插入指向两个分裂所得节点的指针,当然实际上只需要插入一个新增指针)。虽然只要leaf保持非满就一定能成功插入,但是当leaf成为满节点分裂时,会有一个数据向上插入到其父节点中,所以必须保证其父节点也非满,如果父节点被插入后成为满节点还需要继续分裂并向上插入数据,依此类推,相当于所有节点都需要保持非满状态。

在插入时向上调整的过程中看似需要递归回溯,其实可以在向下搜索插入位置时,对于即将搜索的下一个节点,都检查其是否为满节点,如果是满节点就将其分裂。而又因为对于搜索路径上的每个节点都进行判断,所以搜索路径上已经搜索过的节点一定是非满的,那么即将搜索到的节点如果需要分裂,分裂后向上插入的那个数据就一定能够插入成功。综上所述,这样的处理方式一定能够成功执行,并且可以保证数据一定能够插入成功。


分裂节点

这个函数传入的是指向待分裂节点的父节点的指针和指向待分裂节点的指针在其父节点Child数组中的下标。

首先用BTAllocateNode函数新建一个节点NewNode,将待分裂节点的Minimum Degree - 1个数据和Minimum Degree个Child指针转移到NewNode中,同时应当注意NewNode和待分裂节点应该处于同一层,即两者Isleaf成员的值应当保持一致(即下面代码段中的Caution注释行),再将待分裂节点的父节点中Key数组和Child数组的部分元素向右移动,为之后的插入腾出位置,最后将待分裂节点中数据的中值插入其父节点中,并将指向NewNode的指针也插入待分裂节点的父节点中。

void BTChildSplit(PtrBTNode SplitNodeP, int ChildIndex){
    int i;
    PtrBTNode NewNode = BTAllocateNode();
    PtrBTNode FullNode = SplitNodeP->Child[ChildIndex]
  • 6
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值