【数据结构】B树

B树介绍

B树(B-Tree)是一种自平衡的树结构,它维持数据的有序性,并且允许搜索、顺序访问、插入和删除操作都在对数时间内完成。B树是为磁盘和其他直接访问的辅助存储设备而设计的,主要用于数据库和文件系统中。以下是B树的一些主要特点:

B树的特性:

  1. 节点最大和最小孩子数:一个B树的节点可能有许多孩子,从几个到数千个。每个节点除了叶子节点外,都有至少两个孩子。
  2. 节点中的键(Key):每个节点包含一定数量的键。这些键按照升序排列,并分隔开节点指针,使得每个键都是一个子树的分隔键。
  3. 分支因子:B树的每个节点最多包含的子节点数称为分支因子,通常用字母m表示。一个节点的键的数量比它孩子的数量少1,即一个节点包含最多m-1个键。
  4. 平衡性:B树是平衡的,所有叶子节点都在同一层。
  5. 填充因子:为了保持树的平衡,B树在插入和删除操作时会进行节点的分裂和合并。节点的分裂和合并会确保树的高度尽可能低。

B树的操作:

  • 搜索:从根节点开始,根据要查找的键与节点的键进行比较,决定是继续在左子树还是右子树中搜索。
  • 插入:在找到合适的叶子节点后,将新键插入该节点。如果节点中的键的数量超过了m-1,则节点会分裂成两个节点,并将中间的键提升到父节点。
  • 删除:找到包含要删除键的节点,并从节点中移除该键。如果删除后节点中的键的数量少于要求的最小数量,则需要重新分配或合并节点。

B树的应用:

  • 数据库索引:B树由于其高效的磁盘I/O操作,常被用作数据库系统的索引结构。
  • 文件系统:许多文件系统使用B树或其变种(如B+树)来存储文件系统的元数据。
    B树之所以高效,是因为它减少了磁盘I/O操作的次数。在数据库和文件系统中,磁盘I/O是非常耗时的操作,因此减少磁盘访问次数可以显著提高性能。
    以下是一个简单的B树示例,我们将通过一个具体的例子来展示B树的搜索、插入、和删除操作。假设我们有一个B树,其分支因子为4,这意味着每个节点最多可以有3个键和4个子节点。

案例

结构理解

初始状态

首先,我们从一个空的B树开始,然后逐步执行插入操作。

空B树

插入操作

现在,我们将以下键按顺序插入到B树中:10, 20, 5, 6, 12, 30, 7, 17

  1. 插入10
     10
  1. 插入20
     10     20
  1. 插入5
     5      10     20
  1. 插入6
     5      6      10     20
  1. 插入12
     5      6      10     12     20
  1. 插入30
    因为节点已满,我们需要分裂节点:
     6
   /   \
  5     10
       /   \
      12    20
             \
              30
  1. 插入7
     6
   /   \
  5     10
       /   \
      7    12
             \
              20
             /
            30
  1. 插入17
    因为节点已满,我们需要再次分裂节点:
       10
     /    \
    6      20
   / \    /  \
  5   7  12   30
           \
            17

搜索操作

假设我们要搜索键值为17的节点。

  1. 从根节点10开始,因为17 > 10,所以我们搜索右子树。
  2. 在节点20的左子树中,因为17 < 20,我们搜索节点12。
  3. 在节点12的右子树中,我们找到了键17。

删除操作

现在,假设我们要删除键值为7的节点。

  1. 找到键7所在的节点,它是节点6的左孩子。
  2. 删除键7。
  3. 因为删除后节点6仍然有至少2个键,所以不需要进行节点合并或重新分配。
    删除后的B树如下:
       10
     /    \
    6      20
   / \    /  \
  5   7  12   30
           \
            17

需要注意的是删除操作可能会导致节点的合并或重新分配,但在本例中,我们假设删除操作不会触发这些情况。如果删除操作导致节点键的数量少于最小要求,那么需要从兄弟节点借键或与兄弟节点合并,以保持B树的平衡。

C代码实现

#include <stdio.h>
#include <stdlib.h>

#define MAX_KEYS 3
#define MIN_KEYS 2

typedef struct BTreeNode {
    int keys[MAX_KEYS + 1];
    struct BTreeNode *child[MAX_KEYS + 2];
    int num_keys;
    int is_leaf;
} BTreeNode;

BTreeNode *create_node(int is_leaf) {
    BTreeNode *node = (BTreeNode *)malloc(sizeof(BTreeNode));
    node->num_keys = 0;
    node->is_leaf = is_leaf;
    for (int i = 0; i <= MAX_KEYS; i++) {
        node->keys[i] = 0;
        node->child[i] = NULL;
    }
    node->child[MAX_KEYS + 1] = NULL;
    return node;
}

void insert(BTreeNode **root, int key) {
    if (*root == NULL) {
        *root = create_node(1);
        (*root)->keys[0] = key;
        (*root)->num_keys = 1;
        return;
    }

    if ((*root)->num_keys == MAX_KEYS) {
        BTreeNode *new_root = create_node(0);
        new_root->child[0] = *root;
        split_child(new_root, 0);
        insert_non_full(new_root, key);
        *root = new_root;
    } else {
        insert_non_full(*root, key);
    }
}

void insert_non_full(BTreeNode *node, int key) {
    int i = node->num_keys - 1;
    if (node->is_leaf) {
        while (i >= 0 && node->keys[i] > key) {
            node->keys[i + 1] = node->keys[i];
            i--;
        }
        node->keys[i + 1] = key;
        node->num_keys++;
    } else {
        while (i >= 0 && node->keys[i] > key) i--;
        i++;
        if (node->child[i]->num_keys == MAX_KEYS) {
            split_child(node, i);
            if (key > node->keys[i]) i++;
        }
        insert_non_full(node->child[i], key);
    }
}

void split_child(BTreeNode *node, int i) {
    BTreeNode *z = create_node(node->child[i]->is_leaf);
    z->num_keys = MIN_KEYS;
    for (int j = 0; j < MIN_KEYS; j++) {
        z->keys[j] = node->child[i]->keys[j + MIN_KEYS + 1];
    }
    if (!node->child[i]->is_leaf) {
        for (int j = 0; j < MIN_KEYS + 1; j++) {
            z->child[j] = node->child[i]->child[j + MIN_KEYS + 1];
        }
    }
    node->child[i]->num_keys = MIN_KEYS;
    for (int j = node->num_keys; j >= i + 1; j--) {
        node->child[j + 1] = node->child[j];
    }
    node->child[i + 1] = z;
    for (int j = node->num_keys - 1; j >= i; j--) {
        node->keys[j + 1] = node->keys[j];
    }
    node->keys[i] = node->child[i]->keys[MIN_KEYS];
    node->num_keys++;
}

BTreeNode *search(BTreeNode *root, int key) {
    int i = 0;
    while (i < root->num_keys && key > root->keys[i]) {
        i++;
    }
    if (i < root->num_keys && key == root->keys[i]) {
        return root;
    }
    if (root->is_leaf) {
        return NULL;
    } else {
        return search(root->child[i], key);
    }
}

void delete(BTreeNode **root, int key) {
    if (*root == NULL) return;

    int i = 0;
    while (i < (*root)->num_keys && key > (*root)->keys[i]) {
        i++;
    }

    if (i < (*root)->num_keys && key == (*root)->keys[i]) {
        if ((*root)->is_leaf) {
            for (int j = i; j < (*root)->num_keys - 1; j++) {
                (*root)->keys[j] = (*root)->keys[j + 1];
            }
            (*root)->num_keys--;
            if ((*root)->num_keys == 0) {
                free(*root);
                *root = NULL;
            }
        } else {
        
        }
    } else {
        if ((*root)->is_leaf) {
            return; // Key not found in the tree
        }

        // The key might be in the child
        BTreeNode *child = (*root)->child[i];
        if (child->num_keys == MIN_KEYS) {
            // 如果删除操作导致节点键的数量少于最小要求,那么需要从兄弟节点借键或与兄弟节点合并,以保持B树的平衡
        } else {
            delete(&child, key);
        }
    }
}

void print_tree(BTreeNode *node, int level) {
    if (node != NULL) {
        for (int i = 0; i < node->num_keys; i++) {
            print_tree(node->child[i], level + 1);
            for (int j = 0; j < level; j++) printf("   ");
            printf("%d\n", node->keys[i]);
        }
        print_tree(node->child[node->num_keys], level + 1);
    }
}

int main() {
    BTreeNode *root = NULL;

    int keys_to_insert[] = {10, 20, 5, 6, 12, 30, 7, 17};
    int num_keys = sizeof(keys_to_insert) / sizeof(keys_to_insert[0]);

    for (int i = 0; i < num_keys; i++) {
        insert(&root, keys_to_insert[i]);
    }

    printf("B-Tree after insertions:\n");
    print_tree(root, 0);

    BTreeNode *search_result = search(root, 17);
    if (search_result != NULL) {
        printf("Key 17 found in BTree.\n");
    } else {
        printf("Key 17 not found in BTree.\n");
    }

    delete(&root, 7);
    printf("B-Tree after deleting 7:\n");
    print_tree(root, 0);

    return 0;
}

这个程序展示了如何创建一个B树,插入一系列键,搜索一个键,并简单地删除一个键。删除操作只处理了叶子节点的情况,并没有处理节点合并或重新分配的复杂情况。打印函数print_tree用于可视化地展示B树的结构。

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
B树是一种自平衡的树数据结构,常用于数据库和文件系统中,因为它能提供高效的查找、插入和删除操作。这里我将简要介绍B树的基本概念和常见的C++实现框架,但请注意完整的代码会涉及到大量的细节和模板元编程,这里不会列出全部代码。 B树的主要特点: 1. **范围查询**: B树的每个节点可以包含多个关键字,这使得它能够处理大规模的数据集,同时保持查询效率。 2. **自平衡**: 在插入和删除时,B树会自动调整其高度以保持平衡,保证了对数时间复杂度的操作。 **B树基础**: - 每个节点最多有\( m \)个键值(\( m \)是大于1的固定整数),其中\( \lceil \frac{m}{2} \rceil \)是左子树的最大深度,\( \lfloor \frac{m}{2} \rfloor \)是右子树的最大深度。 - 根节点至少有两个键值,叶子节点没有子节点,内部节点至少有一个子节点。 - 插入和删除时,可能会导致节点分裂或合并,以保持平衡。 **C++中的B树核心结构**: ```cpp template <typename Key, typename MKey = Key, typename Value = void, int Order = 20> class BTreeNode { // 数据结构和方法... }; ``` 这里`Order`是一个可变参数,定义了每个节点的最小容量。实际的实现会包含节点的指针数组、大小信息、平衡操作的辅助函数等。 **相关问题--:** 1. B树的关键字是如何组织在节点中的? 2. 描述一下B树插入和删除操作的具体步骤。 3. 如何在C++中创建一个B树的根节点?

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值