数据结构 - b树

b树和红黑树不一样,b树是一个n叉树,也就是说可以定义每个节点下的子树的最大数量,因此b树和红黑树相比在于树的层级较低,搜索遍历时更快。

b树有以下特点(M阶b树):

  • 根节点至少有两颗子树
  • 除根节点以外,每个节点至少有M/2颗子树
  • 关键子树的数量满足: (M/2 -1 ) <= n <= M-1
  • 所有叶子节点在同一层
  • 有M棵 子树的分支结点则存在M -1 个关键字

对于最后一条,意味着在插入的时候,都是插入到叶子节点上,那么这时候要求计算其子树的数量是否达到要求,例如6叉的b树,那么每个节点的子节点,不能超过5个子树,因此在达到5的时候,就要考虑进行分裂。

对于删除的时候,可能会导致子树下也不满足分支节点的最低要求(M/2) -1, 以6叉为例,那么这里就是2,那么这里就需要合并。如果是父节点也不满足条件的时候,那么这时候需要考虑借位的操作。因此删除前先借位或合并,再删除

一: b树结构定义

#define DEGREE 3;
typedef int KEY_VALUE;

typedef struct _bree_node
{

    KEY_VALUE *keys;                // 2 * DEGREE -1, 保留一个存自身数据
    struct bree_node_t **childrens; // 每个key下面有 2 * DEGREE 树

    int num;  // 当前真实的key数量
    int leaf; // 是否为叶子节点
} bree_node_t;

typedef struct _bree_tree
{
   bree_tree_t *root;
} bree_tree_t;


btree_node *btree_create_node(int leaf){

   //相比malloc是会自动置零
   btree_node *node = calloc(1, sizeof(bree_node_t));
   if (node == NULL) return;

    node->leaf = leaf;
    node->keys = (KEY_VALUE *)calloc(2 * DEGREE -1, sizeof(KEY_VALUE));
    node->childres = (bree_node_t **)calloc(2 *DEGREE, sizeof(bree_node_t));
    node->num = 0;

    return node;
}

void btree_destroy_node(btree_node *node) {
    //check
    assert(node);

    free(node->childrens);
    free(node-keys);
    free(node);
}

需要留意的是创建的时候,每个childrens下是2 * DEGREE, 因为是6叉树,但是keys只有 2 * DEGREE -1, 要预留一个位置保存数据。

二: b树分裂

注意这里不是key值指向子树,而是key值之间的间隙指向子树,代表的是key值间的区间中的值

对于上面这种情况,这时候插入了一个节点在子树下,发现右边的子树这里需要分裂,因为下一次插入的时候就不满足条件了,因此这里分解步骤如下所示:

  • 第一步首先申请一个新的节点为z,然后把要被分裂的节点y的后半部分复制到z中。

  • 第二步就是挪动被分裂节点的父节点的children指向,因为这里添加了新的节点z

这里还需要注意,如果是非叶子节点的分裂,那么这里需要设置childrens指向

  •  第三步则是把要分裂的节点的中间数值,放到父节点中

注意这里第三步插入的时候,不一定是插入到父节点的后面,也可能是中间,这里是一个简化

 因此该函数的完整代码如下所示:

//入参x代表要分裂的子树的父节点, i表示其父节点下的第几颗子树
//这里是以父节点为视角的:
void btree_split_child(btree_tree_t *T, btree_nde *x, int i) {

    //要被分裂的节点
    btree_node_t *y = x->childrens[i];
    
    //创建新的节点,用于接收被分裂的后半部分的内容
    btree_node_t *z = btree_create_node(y->leaf);
    //新创建的节点接收的数据数量
    z->num = DEGREE - 1;

    //第一步:
    //把要分裂的后半部分数据挪到新申请的节点上
    for(i = 0, i< DEGREE -1 ; i++){
        z->keys[i] = y->keys[DEGREE + i];
    }

    if (y->leaf == 0) {
        //inner
        for (i = 0; i < DEGREE; i++) {
            z->childrens[i] = y->childrens[DEGREE+i];
        }
    }

    y->num = DEGREE - 1;


    //第二步:
    //挪动被分裂节点的父节点的指向,因为这里新添加了z节点
    for (j = x-> num; j > = i+1; j--) {
        x->childrens[j+1] = x->childrens[j];
    }
    x->childrens[i+1] = z;


    //第三步:
    for (j = x->num -1; j >=i; j--) {
        x->keys[j+1] = x->keys[j];
    }

    x->keys[i] = y->keys[t-1];
    x->num += 1;
}

三: 根节点分裂

如果当前只有一个节点,也就是根节点,达到要分裂的条件时,这里会一分为三,需要多一步才能进行分裂,过程如下所示:

 这里就是先申请一个空的节点,并且第0棵子树指向该节点,然后进行插入分裂。

void btree_insert(btree_tree_t *T, KEY_VALUE key) {
    btree_node_t *old_root = T->root;
    
    if (root->num == 2 * DEGREE -1) {
        //根节点要进行分裂

        //1). 先申请一个空的节点,并且成为根节点
        btree_node_t *node =  btree_create_node(0);
        T->root = node;

        node->childrens[0] = old_root;
        btree_split_child(T, node, 0);
  
        //下面进行插入

    } else {

    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值