B-树的学习笔记与C实现

(学习的主要对象是《算法导论》上B-树章节)

应用环境:

  辅存和主存的矛盾,主存只能维持有限的页数,其他页存于辅存上,使用时调入内存。

 

B树的定义:

  是一个具有如下性质的有根树:

  (1)每个结点x有以下域:

    (a) n[x],存放结点x的关键字数;

    (b) n[x]个关键字本身,以非降序存放;

    (c) leaf[x],1代表x是叶子,0代表x是内结点。

  (2)每个内结点包含n[x]+1各指向其子女的指针。叶结点对这个域没有定义。

  (3)各关键字对其各子树关键字范围进行分隔。

  (4)每个叶结点深度相同。

  (5)每个结点包含的关键字有上界和下界,用t表示最小度数,则有

    (a)每个非根结点至少t-1各关键字。每个非根的内结点至少t个子女。若树非空,则根至少有一个关键字。

    (b)每个结点包含至多2t-1个关键字。因此一个内结点至多可以有2t个子女。

相关算法简述:

  搜索算法:

    类似于二叉查找树。

  分裂算法:

    对于关键字达到2t-1时的满结点(可能)将做的操作。简述为,将中间关键字提升至父结点,同时将原结点分裂成两个。

  插入算法:

    利用了分裂算法,在插入时,逐步下降到要查入的结点,沿途对满结点进行分裂,若不满,直接插入即可,用辅助算法insert_nonful实现。

  删除算法:

    比较复杂,逐步下降时,对于将来不满足关键字数大于t-1的结点做出调整。调整有多种形式:合并两个关键字为t-1的兄弟;下降父结点至一个关键字所在子树的子结点,同时上升它的一个关键字大于等于t的兄弟结点的关键字至父结点;对于逐步下降后含关键字的叶子,直接删除关键字。

    这个简述看上去比较混乱,直接理解算法即可,或者参考算法导论上相关叙述。。

 

心得体会:

  与红黑树相反,B-树的插入和删除在下降时进行调整,而前者是先操作,然后逐步向上调整使其满足性质。因此B-树不需要指向父亲的指针。

  同时,B-树由于是在下降过程中调整,因此它不能直接对非根结点调用delete,这样会导致调整不完全。

  而且对于B-树关键字和子结点指针操作时,下标比较容易引起混乱。有一个小技巧需要可以减少混乱:下标为i的关键字,其左边的孩子下标也是i。不过编写算法时最好画个示意图,比较清楚。

 

以下是自己编写的C语言版本的B-树,其中各个操作已经过验证和调整,暂未发现遗留的bug。如果想实现自己的测试过程,调整main函数里面的操作即可。

B-树的学习笔记与C实现
#include <stdio.h>
#include <stdlib.h>

#define BT_T 3//B树的度    

struct bnode *bt_search(struct bnode *, char);
struct bnode *bt_creat();
int bt_split_child(struct bnode *, int, struct bnode *);
struct bnode *bt_insert(struct bnode *, char);
int bt_insert_nonful(struct bnode *, char);
int bt_delete(struct bnode*, char);

struct bnode {
    int num;
    int leaf; //1 is leaf
    char value[2*BT_T -1];
    struct bnode *child[2*BT_T];
};



struct bnode *bt_search(struct bnode *p, char k) {
    int i;
    i = 0;
    while ((i < p->num) && (k > p->value[i])) 
        i++;

    if ((i<=p->num) && (k == p->value[i])) {
        printf("[search]%c's num is %d\n",k,i);
        return p;
    }
    if (p->leaf) {
        printf("not found.\n");
        return NULL;
    }
    else {
        printf("DISK-READ: c%d\n", i); //DISK-READ()
        return bt_search(p->child[i],k);
    }
}

struct bnode *bt_creat() {
    struct bnode *p;
    p = (struct bnode *)malloc(sizeof(struct bnode));
    p->leaf = 1;
    p->num = 0;
    printf("DISK-WRITE: root of new T\n");
    return p;
}

int bt_split_child(struct bnode *x, int i, struct bnode *y) {
    int j;
    struct bnode *z;
    z = (struct bnode *)malloc(sizeof(struct bnode));
    printf("in split\n");
    z->leaf = y->leaf;
    z->num = BT_T -1 ;
    for (j=0;j<BT_T-1;j++)
        z->value[j] = y->value[j+BT_T];
    if (!(y->leaf))
        for(j=0;j<=BT_T-1;j++) //there was a bug.
            z->child[j] = y->child[j+BT_T];
    y->num = BT_T -1;
    for(j=x->num +1;j>=i+1;j--)//
        x->child[j+1] = x->child[j];
    x->child[i+1] = z;
    for(j=x->num -1;j>=i-1;j--)
        x->value[j+1] = x->value[j];
    x->value[i] = y->value[BT_T-1];
    x->num++;
    printf("DISK-WRITE(y):in split\n");
    printf("DISK-WRITE(z):in split\n");    
    printf("DISK-WRITE(x):in split\n");
}

struct bnode* bt_insert(struct bnode *x, char k) {//x is root
    struct bnode *s;
    if (x->num == 2*BT_T-1) {
        s = (struct bnode *)malloc(sizeof(struct bnode));
        s->leaf = 0;
        s->num = 0;
        s->child[0] = x;
        bt_split_child(s,0,x);
        bt_insert_nonful(s,k);
        return s;
    }
    else {
        bt_insert_nonful(x,k);
        return x;
    }
}

int bt_insert_nonful(struct bnode *x, char k) {
    int i;
    struct bnode *p;
    i = x->num-1;
    if (x->leaf)    {
        while((i>=0)&&(k<x->value[i])) {
            x->value[i+1] = x->value[i];
            i--;
        }
        x->value[i+1] = k;
        printf("(!!!!)%c %d\n",x->value[i+1],i+1);
        x->num = x->num+1;
        printf("DISK-WRITE(x):in bt_insert_nonful\n");
    }
    else
    {
        while((i>=0)&&(k<x->value[i-1]))
            i=i--;
        i++;
        printf("DISK-READ(c%d[x]):in insert_nonful\n",i);
        p = x->child[i];
        if (p->num == (2*BT_T - 1)) {
            bt_split_child(x,i,p);
            if (k>x->value[i])
                i++;
        }
        bt_insert_nonful(x->child[i],k);//there was a bug: bt_insert_nonful(p,k);
    }
    return 1;
}

int bt_delete(struct bnode* x, char k) {
    int i,j,lnum,rnum;
    struct bnode *p;
    i=0;
    while ((i<x->num) && (k>x->value[i]))
        i++;
    if ((i<=x->num) && (k == x->value[i])) {
        if(x->leaf) {//情况(1),不能对叶结点的指针直接调用bt_delete(),这样相当于跳过了情况(3)
            for(;i<x->num-1;i++) 
                x->value[i] = x->value[i+1];
            x->num --;
            return 1;
        }
        else {    //情况2
            lnum = x->child[i]->num;
            rnum = x->child[i+1]->num;
            if (lnum >= BT_T) {
                x->value[i] = x->child[i]->value[lnum-1];
                bt_delete(x->child[i],x->value[i]);
                return 2;
            }
            else if (rnum >= BT_T) {
                x->value[i] = x->child[i+1]->value[0];
                bt_delete(x->child[i+1],x->value[0]);
                return 2;
            }
            else {
                //合并k两个孩子结点,并把k下降到这个结点
                x->child[i]->value[BT_T-1] = k;
                for(j=0;j<BT_T-1;j++)    {
                    x->child[i]->value[BT_T+j] = x->child[i+1]->value[j];
                    x->child[i]->child[BT_T+j] = x->child[i+1]->child[j];
                    }
                x->child[i]->num = 2*BT_T -1;
                
                //修改x,使其原k右边的孩子与x断开
                for(j=i;j<x->num-1;j++)    {
                    x->value[j] = x->value[j+1];
                    x->child[j+1] = x->child[j+2];
                    }
                x->num--;

                //递归删除k
                bt_delete(x->child[i],k);
                return 2;
            }
        }
    }
    else {
        if(bt_search(x->child[i],k) == NULL) {
            printf("[delete]not found!");
            return 0;
            }
        p = x->child[i];
        if (x->child[i]->num >= BT_T) {
            bt_delete(x->child[i],k);
            return 3;
            }
        else if ((i>0)&&(x->child[i-1]->num >= BT_T)) {//情况3a其1,左兄弟可用
            for(j=BT_T-2;j>=0;j--)
                p->value[j+1] = p->value[j];
            p->value[0] = x->value[i-1];
            
            for(j=BT_T;j>=1;j--)
                p->child[j] = p->child[j-1];
            p->child[0] = x->child[i-1]->child[x->child[i-1]->num];
            x->value[i-1] = x->child[i-1]->value[x->child[i-1]->num-1];
            x->child[i-1]->num--;
            bt_delete(x->child[i],k);
            return 3;
            }        
        else if ((i<x->num-1)&&(x->child[i+1]->num >= BT_T)) {//情况3b其2,右兄弟可用
            p->num++;
            p->value[p->num-1] = x->value[i];
            p->child[p->num] = x->child[i+1]->child[0];
            x->value[i] = x->child[i+1]->value[0];

            p=x->child[i+1];//为了便于编码

            for(j=0;j<p->num-1;j--)
                p->value[j] = p->value[j+1];
            
            for(j=0;j<p->num;j--)
                p->child[j] = p->child[j+1];
            p->num--;
            bt_delete(x->child[i],k);
            return 3;
            }
        else if (i>0) {//情况3b其1,与左兄弟合并
            x->child[i-1]->value[BT_T-1] = x->value[i-1];
            for(j=0;j<BT_T-1;j++)
                x->child[i-1]->value[BT_T+j] = x->child[i]->value[j];
            for(j=0;j<=BT_T-1;j++)
                x->child[i-1]->child[BT_T+j] = x->child[i]->child[j];
            for(j=i-1;j<=x->num-2;j++) {
                x->value[j]=x->value[j+1];
                x->child[j+1] = x->child[j+2];
                }
            x->num--;
            x->child[i-1]->num = 2*BT_T -1;
            bt_delete(x->child[i-1],k);
            return 3;
            }
        else if (i<x->num-1) {//情况3b其2,与右兄弟合并
            x->child[i]->value[BT_T-1] = x->value[i];
            for(j=0;j<=BT_T-1;j++)
                x->child[i]->value[BT_T+j] = x->child[i+1]->value[j];
            for(j=0;j<=BT_T;j++)
                x->child[i]->child[BT_T+j] = x->child[i+1]->child[j];
            for(j=i;j<x->num-1;j++) {
                x->value[i] = x->value[i+1];
                x->child[j+1] = x->child[j+2];
            }
            x->num--;
            x->child[i]->num = 2*BT_T -1;
            bt_delete(x->child[i],k);
            return 3;
            }
    }
}        

int main() {
    struct bnode *p,*root;
    char a;
    root = bt_creat();
    for (a='A';a<='Z';a++) 
        if ((a!='H')&&(a!='I')) 
            root = bt_insert(root,a);
 
    bt_delete(root,'M');
    bt_search(root,'N');    
    bt_search(root,'O');

    return 1;
}
B-树的学习笔记与C实现







本文转自五岳博客园博客,原文链接:www.cnblogs.com/wuyuegb2312/archive/2012/10/23/2735257.html,如需转载请自行联系原作者
上一篇:[20121101]物化视图与表(Materialized Views and Tables).txt


下一篇:数据结构C#版笔记--双向链表(DbLinkList)